Merge pull request #1637 from molecula/parallel-backup

CORE-639: Parallelize backup
This commit is contained in:
Ben Johnson 2021-06-15 17:44:17 -06:00 committed by GitHub
commit e082f6bce0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 68 additions and 16 deletions

View file

@ -38,6 +38,7 @@ Backs up a pilosa server to a local, tar-formatted snapshot file.
flags := ccmd.Flags()
flags.StringVarP(&cmd.OutputDir, "output", "o", "", "output dir to write to")
flags.BoolVar(&cmd.NoSync, "no-sync", false, "disable file sync")
flags.IntVar(&cmd.Concurrency, "concurrency", cmd.Concurrency, "number of concurrent backup goroutines")
flags.StringVar(&cmd.Host, "host", "localhost:10101", "host:port of Pilosa.")
ctl.SetTLSConfig(flags, "", &cmd.TLS.CertificatePath, &cmd.TLS.CertificateKeyPath, &cmd.TLS.CACertPath, &cmd.TLS.SkipVerify, &cmd.TLS.EnableClientVerification)
return ccmd

View file

@ -28,6 +28,7 @@ import (
"github.com/pilosa/pilosa/v2/http"
"github.com/pilosa/pilosa/v2/server"
"github.com/pilosa/pilosa/v2/topology"
"golang.org/x/sync/errgroup"
)
// BackupCommand represents a command for backing up a Pilosa node.
@ -43,6 +44,9 @@ type BackupCommand struct { // nolint: maligned
// If true, skips file sync.
NoSync bool
// Number of concurrent backup goroutines running at a time.
Concurrency int
// Reusable client.
client pilosa.InternalClient
@ -55,7 +59,8 @@ type BackupCommand struct { // nolint: maligned
// NewBackupCommand returns a new instance of BackupCommand.
func NewBackupCommand(stdin io.Reader, stdout, stderr io.Writer) *BackupCommand {
return &BackupCommand{
CmdIO: pilosa.NewCmdIO(stdin, stdout, stderr),
CmdIO: pilosa.NewCmdIO(stdin, stdout, stderr),
Concurrency: 1,
}
}
@ -64,6 +69,8 @@ func (cmd *BackupCommand) Run(ctx context.Context) (err error) {
// Validate arguments.
if cmd.OutputDir == "" {
return fmt.Errorf("-o flag required")
} else if cmd.Concurrency <= 0 {
return fmt.Errorf("concurrency must be at least one")
}
// Parse TLS configuration for node-specific clients.
@ -156,16 +163,8 @@ func (cmd *BackupCommand) backupIndex(ctx context.Context, ii *pilosa.IndexInfo)
logger := cmd.Logger()
logger.Printf("backing up index: %q", ii.Name)
shards, err := cmd.client.AvailableShards(ctx, ii.Name)
if err != nil {
return fmt.Errorf("cannot find available shards for index %q: %w", ii.Name, err)
}
// Back up all bitmap data for the index.
for _, shard := range shards {
if err := cmd.backupShard(ctx, ii.Name, shard); err != nil {
return fmt.Errorf("cannot backup shard %d on index %q: %w", shard, ii.Name, err)
}
if err := cmd.backupShards(ctx, ii); err != nil {
return err
}
// Back up translation data after bitmap data so we ensure we can translate all data.
@ -183,6 +182,39 @@ func (cmd *BackupCommand) backupIndex(ctx context.Context, ii *pilosa.IndexInfo)
return nil
}
func (cmd *BackupCommand) backupShards(ctx context.Context, ii *pilosa.IndexInfo) error {
shards, err := cmd.client.AvailableShards(ctx, ii.Name)
if err != nil {
return fmt.Errorf("cannot find available shards for index %q: %w", ii.Name, err)
}
// Back up all bitmap data for the index.
ch := make(chan uint64, len(shards))
for _, shard := range shards {
ch <- shard
}
close(ch)
g, ctx := errgroup.WithContext(ctx)
for i := 0; i < cmd.Concurrency; i++ {
g.Go(func() error {
for {
select {
case <-ctx.Done():
return ctx.Err()
case shard, ok := <-ch:
if !ok {
return nil
} else if err := cmd.backupShard(ctx, ii.Name, shard); err != nil {
return fmt.Errorf("cannot backup shard %d on index %q: %w", shard, ii.Name, err)
}
}
}
})
}
return g.Wait()
}
// backupShard backs up a single shard from a single index.
func (cmd *BackupCommand) backupShard(ctx context.Context, indexName string, shard uint64) (err error) {
nodes, err := cmd.client.FragmentNodes(ctx, indexName, shard)
@ -234,14 +266,33 @@ func (cmd *BackupCommand) backupShardNode(ctx context.Context, indexName string,
}
func (cmd *BackupCommand) backupIndexTranslateData(ctx context.Context, name string) error {
// TODO: Fetch holder partition count.
partitionN := topology.DefaultPartitionN
// Back up all bitmap data for the index.
ch := make(chan int, partitionN)
for partitionID := 0; partitionID < partitionN; partitionID++ {
if err := cmd.backupIndexPartitionTranslateData(ctx, name, partitionID); err != nil {
return fmt.Errorf("cannot backup index translation data for partition %d on %q: %w", partitionID, name, err)
}
ch <- partitionID
}
return nil
close(ch)
g, ctx := errgroup.WithContext(ctx)
for i := 0; i < cmd.Concurrency; i++ {
g.Go(func() error {
for {
select {
case <-ctx.Done():
return ctx.Err()
case partitionID, ok := <-ch:
if !ok {
return nil
} else if err := cmd.backupIndexPartitionTranslateData(ctx, name, partitionID); err != nil {
return fmt.Errorf("cannot backup index translation data for partition %d on %q: %w", partitionID, name, err)
}
}
}
})
}
return g.Wait()
}
func (cmd *BackupCommand) backupIndexPartitionTranslateData(ctx context.Context, name string, partitionID int) error {