Skip to content

Commit 0e89f7a

Browse files
authored
Merge pull request #1107 from Altinity/replicated_copy_to_attached
implement --replicated_copy_to_detached=1 option in restore command
2 parents c2edb9b + 667b3f2 commit 0e89f7a

File tree

7 files changed

+160
-74
lines changed

7 files changed

+160
-74
lines changed

ChangeLog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# v2.6.6
22

33
IMPROVEMENTS
4+
- Add `--replicated-copy-to-detached` option to restore command, it allows faster restore on multiple replicas, second and follow replicas will handle ATTACH_PART restore events, fix [1104](https://github.com/Altinity/clickhouse-backup/issues/1104)
45
- Add `CLICKHOUSE_SKIP_DISKS` config option, to allow skip backup some disk like object disks, fix [908](https://github.com/Altinity/clickhouse-backup/issues/908)
56
- Add simple check free size before disk download, to avoid 100% disk space usage, fix [878](https://github.com/Altinity/clickhouse-backup/issues/878)
67
- Add `--restore-schema-as-attach` CLI parameter and `POST /backup/restore/{name}`, fix [868](https://github.com/Altinity/clickhouse-backup/issues/868)

ReadMe.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,7 @@ Look at the system.parts partition and partition_id fields for details https://c
754754
--configs-only Restore 'clickhouse-server' configuration files only, will skip backup data, will backup schema only if --schema added
755755
--skip-projections db_pattern.table_pattern:projections_pattern Skip make hardlinks to *.proj/* files during backup restoring, format db_pattern.table_pattern:projections_pattern, use https://pkg.go.dev/path/filepath#Match syntax
756756
--resume, --resumable Will resume download for object disk data
757+
--replicated-copy-to-detached Copy data to detached folder for Replicated*MergeTree tables but skip ATTACH PART step
757758
758759
```
759760
### CLI command - restore_remote
@@ -787,6 +788,7 @@ Look at the system.parts partition and partition_id fields for details https://c
787788
--configs-only Restore 'clickhouse-server' configuration files only, will skip backup data, will backup schema only if --schema added
788789
--skip-projections db_pattern.table_pattern:projections_pattern Skip make hardlinks to *.proj/* files during backup restoring, format db_pattern.table_pattern:projections_pattern, use https://pkg.go.dev/path/filepath#Match syntax
789790
--resume, --resumable Save intermediate download state and resume download if backup exists on remote storage, ignored with 'remote_storage: custom' or 'use_embedded_backup_restore: true'
791+
--replicated-copy-to-detached Copy data to detached folder for Replicated*MergeTree tables but skip ATTACH PART step
790792
791793
```
792794
### CLI command - delete

cmd/clickhouse-backup/main.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ func main() {
395395
UsageText: "clickhouse-backup restore [-t, --tables=<db>.<table>] [-m, --restore-database-mapping=<originDB>:<targetDB>[,<...>]] [--tm, --restore-table-mapping=<originTable>:<targetTable>[,<...>]] [--partitions=<partitions_names>] [-s, --schema] [-d, --data] [--rm, --drop] [-i, --ignore-dependencies] [--rbac] [--configs] [--resume] <backup_name>",
396396
Action: func(c *cli.Context) error {
397397
b := backup.NewBackuper(config.GetConfigFromCli(c))
398-
return b.Restore(c.Args().First(), c.String("tables"), c.StringSlice("restore-database-mapping"), c.StringSlice("restore-table-mapping"), c.StringSlice("partitions"), c.StringSlice("skip-projections"), c.Bool("schema"), c.Bool("data"), c.Bool("drop"), c.Bool("ignore-dependencies"), c.Bool("rbac"), c.Bool("rbac-only"), c.Bool("configs"), c.Bool("configs-only"), c.Bool("resume"), c.Bool("restore-schema-as-attach"), version, c.Int("command-id"))
398+
return b.Restore(c.Args().First(), c.String("tables"), c.StringSlice("restore-database-mapping"), c.StringSlice("restore-table-mapping"), c.StringSlice("partitions"), c.StringSlice("skip-projections"), c.Bool("schema"), c.Bool("data"), c.Bool("drop"), c.Bool("ignore-dependencies"), c.Bool("rbac"), c.Bool("rbac-only"), c.Bool("configs"), c.Bool("configs-only"), c.Bool("resume"), c.Bool("restore-schema-as-attach"), c.Bool("replicated-copy-to-detached"), version, c.Int("command-id"))
399399
},
400400
Flags: append(cliapp.Flags,
401401
cli.StringFlag{
@@ -479,6 +479,11 @@ func main() {
479479
Hidden: false,
480480
Usage: "Use DETACH/ATTACH instead of DROP/CREATE for schema restoration",
481481
},
482+
cli.BoolFlag{
483+
Name: "replicated-copy-to-detached",
484+
Hidden: false,
485+
Usage: "Copy data to detached folder for Replicated*MergeTree tables but skip ATTACH PART step",
486+
},
482487
),
483488
},
484489
{
@@ -487,7 +492,7 @@ func main() {
487492
UsageText: "clickhouse-backup restore_remote [--schema] [--data] [-t, --tables=<db>.<table>] [-m, --restore-database-mapping=<originDB>:<targetDB>[,<...>]] [--tm, --restore-table-mapping=<originTable>:<targetTable>[,<...>]] [--partitions=<partitions_names>] [--rm, --drop] [-i, --ignore-dependencies] [--rbac] [--configs] [--skip-rbac] [--skip-configs] [--resumable] <backup_name>",
488493
Action: func(c *cli.Context) error {
489494
b := backup.NewBackuper(config.GetConfigFromCli(c))
490-
return b.RestoreFromRemote(c.Args().First(), c.String("tables"), c.StringSlice("restore-database-mapping"), c.StringSlice("restore-table-mapping"), c.StringSlice("partitions"), c.StringSlice("skip-projections"), c.Bool("schema"), c.Bool("d"), c.Bool("rm"), c.Bool("i"), c.Bool("rbac"), c.Bool("rbac-only"), c.Bool("configs"), c.Bool("configs-only"), c.Bool("resume"), c.Bool("restore-schema-as-attach"), version, c.Int("command-id"))
495+
return b.RestoreFromRemote(c.Args().First(), c.String("tables"), c.StringSlice("restore-database-mapping"), c.StringSlice("restore-table-mapping"), c.StringSlice("partitions"), c.StringSlice("skip-projections"), c.Bool("schema"), c.Bool("d"), c.Bool("rm"), c.Bool("i"), c.Bool("rbac"), c.Bool("rbac-only"), c.Bool("configs"), c.Bool("configs-only"), c.Bool("resume"), c.Bool("restore-schema-as-attach"), c.Bool("replicated-copy-to-detached"), version, c.Int("command-id"))
491496
},
492497
Flags: append(cliapp.Flags,
493498
cli.StringFlag{

pkg/backup/restore.go

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import (
4444
var CreateDatabaseRE = regexp.MustCompile(`(?m)^CREATE DATABASE (\s*)(\S+)(\s*)`)
4545

4646
// Restore - restore tables matched by tablePattern from backupName
47-
func (b *Backuper) Restore(backupName, tablePattern string, databaseMapping, tableMapping, partitions, skipProjections []string, schemaOnly, dataOnly, dropExists, ignoreDependencies, restoreRBAC, rbacOnly, restoreConfigs, configsOnly, resume, schemaAsAttach bool, backupVersion string, commandId int) error {
47+
func (b *Backuper) Restore(backupName, tablePattern string, databaseMapping, tableMapping, partitions, skipProjections []string, schemaOnly, dataOnly, dropExists, ignoreDependencies, restoreRBAC, rbacOnly, restoreConfigs, configsOnly, resume, schemaAsAttach, replicatedCopyToDetached bool, backupVersion string, commandId int) error {
4848
ctx, cancel, err := status.Current.GetContextWithCancel(commandId)
4949
if err != nil {
5050
return err
@@ -239,7 +239,7 @@ func (b *Backuper) Restore(backupName, tablePattern string, databaseMapping, tab
239239

240240
}
241241
if dataOnly || (schemaOnly == dataOnly && !rbacOnly && !configsOnly) {
242-
if err := b.RestoreData(ctx, backupName, backupMetadata, dataOnly, metadataPath, tablePattern, partitions, skipProjections, disks, version); err != nil {
242+
if err := b.RestoreData(ctx, backupName, backupMetadata, dataOnly, metadataPath, tablePattern, partitions, skipProjections, disks, version, replicatedCopyToDetached); err != nil {
243243
return err
244244
}
245245
}
@@ -1286,7 +1286,7 @@ func (b *Backuper) dropExistsTables(tablesForDrop ListOfTables, ignoreDependenci
12861286
}
12871287

12881288
// RestoreData - restore data for tables matched by tablePattern from backupName
1289-
func (b *Backuper) RestoreData(ctx context.Context, backupName string, backupMetadata metadata.BackupMetadata, dataOnly bool, metadataPath, tablePattern string, partitions, skipProjections []string, disks []clickhouse.Disk, version int) error {
1289+
func (b *Backuper) RestoreData(ctx context.Context, backupName string, backupMetadata metadata.BackupMetadata, dataOnly bool, metadataPath, tablePattern string, partitions, skipProjections []string, disks []clickhouse.Disk, version int, replicatedCopyToDetached bool) error {
12901290
var err error
12911291
startRestoreData := time.Now()
12921292
diskMap := make(map[string]string, len(disks))
@@ -1322,7 +1322,7 @@ func (b *Backuper) RestoreData(ctx context.Context, backupName string, backupMet
13221322
if b.isEmbedded {
13231323
err = b.restoreDataEmbedded(ctx, backupName, dataOnly, version, tablesForRestore, partitionsNameList)
13241324
} else {
1325-
err = b.restoreDataRegular(ctx, backupName, backupMetadata, tablePattern, tablesForRestore, diskMap, diskTypes, disks, skipProjections)
1325+
err = b.restoreDataRegular(ctx, backupName, backupMetadata, tablePattern, tablesForRestore, diskMap, diskTypes, disks, skipProjections, replicatedCopyToDetached)
13261326
}
13271327
if err != nil {
13281328
return err
@@ -1338,7 +1338,7 @@ func (b *Backuper) restoreDataEmbedded(ctx context.Context, backupName string, d
13381338
return b.restoreEmbedded(ctx, backupName, false, dataOnly, version, tablesForRestore, partitionsNameList)
13391339
}
13401340

1341-
func (b *Backuper) restoreDataRegular(ctx context.Context, backupName string, backupMetadata metadata.BackupMetadata, tablePattern string, tablesForRestore ListOfTables, diskMap, diskTypes map[string]string, disks []clickhouse.Disk, skipProjections []string) error {
1341+
func (b *Backuper) restoreDataRegular(ctx context.Context, backupName string, backupMetadata metadata.BackupMetadata, tablePattern string, tablesForRestore ListOfTables, diskMap, diskTypes map[string]string, disks []clickhouse.Disk, skipProjections []string, replicatedCopyToDetached bool) error {
13421342
if len(b.cfg.General.RestoreDatabaseMapping) > 0 {
13431343
tablePattern = b.changeTablePatternFromRestoreMapping(tablePattern, "database")
13441344
}
@@ -1398,11 +1398,11 @@ func (b *Backuper) restoreDataRegular(ctx context.Context, backupName string, ba
13981398
restoreBackupWorkingGroup.Go(func() error {
13991399
// https://github.com/Altinity/clickhouse-backup/issues/529
14001400
if b.cfg.ClickHouse.RestoreAsAttach {
1401-
if restoreErr := b.restoreDataRegularByAttach(restoreCtx, backupName, backupMetadata, table, diskMap, diskTypes, disks, dstTable, skipProjections, logger); restoreErr != nil {
1401+
if restoreErr := b.restoreDataRegularByAttach(restoreCtx, backupName, backupMetadata, table, diskMap, diskTypes, disks, dstTable, skipProjections, logger, replicatedCopyToDetached); restoreErr != nil {
14021402
return restoreErr
14031403
}
14041404
} else {
1405-
if restoreErr := b.restoreDataRegularByParts(restoreCtx, backupName, backupMetadata, table, diskMap, diskTypes, disks, dstTable, skipProjections, logger); restoreErr != nil {
1405+
if restoreErr := b.restoreDataRegularByParts(restoreCtx, backupName, backupMetadata, table, diskMap, diskTypes, disks, dstTable, skipProjections, logger, replicatedCopyToDetached); restoreErr != nil {
14061406
return restoreErr
14071407
}
14081408
}
@@ -1428,7 +1428,7 @@ func (b *Backuper) restoreDataRegular(ctx context.Context, backupName string, ba
14281428
return nil
14291429
}
14301430

1431-
func (b *Backuper) restoreDataRegularByAttach(ctx context.Context, backupName string, backupMetadata metadata.BackupMetadata, table metadata.TableMetadata, diskMap, diskTypes map[string]string, disks []clickhouse.Disk, dstTable clickhouse.Table, skipProjections []string, logger zerolog.Logger) error {
1431+
func (b *Backuper) restoreDataRegularByAttach(ctx context.Context, backupName string, backupMetadata metadata.BackupMetadata, table metadata.TableMetadata, diskMap, diskTypes map[string]string, disks []clickhouse.Disk, dstTable clickhouse.Table, skipProjections []string, logger zerolog.Logger, replicatedCopyToDetached bool) error {
14321432
if err := filesystemhelper.HardlinkBackupPartsToStorage(backupName, table, disks, diskMap, dstTable.DataPaths, skipProjections, b.ch, false); err != nil {
14331433
return fmt.Errorf("can't copy data to storage '%s.%s': %v", table.Database, table.Table, err)
14341434
}
@@ -1442,13 +1442,18 @@ func (b *Backuper) restoreDataRegularByAttach(ctx context.Context, backupName st
14421442
if size > 0 {
14431443
logger.Info().Str("duration", utils.HumanizeDuration(time.Since(start))).Str("size", utils.FormatBytes(uint64(size))).Msg("download object_disks finish")
14441444
}
1445-
if err := b.ch.AttachTable(ctx, table, dstTable); err != nil {
1446-
return fmt.Errorf("can't attach table '%s.%s': %v", table.Database, table.Table, err)
1445+
// Skip ATTACH TABLE for Replicated*MergeTree tables if replicatedCopyToDetached is true
1446+
if !replicatedCopyToDetached || !strings.Contains(dstTable.Engine, "Replicated") {
1447+
if err := b.ch.AttachTable(ctx, table, dstTable); err != nil {
1448+
return fmt.Errorf("can't attach table '%s.%s': %v", table.Database, table.Table, err)
1449+
}
1450+
} else {
1451+
logger.Info().Msg("skipping ATTACH TABLE for Replicated*MergeTree table due to --replicated-copy-to-detached flag")
14471452
}
14481453
return nil
14491454
}
14501455

1451-
func (b *Backuper) restoreDataRegularByParts(ctx context.Context, backupName string, backupMetadata metadata.BackupMetadata, table metadata.TableMetadata, diskMap, diskTypes map[string]string, disks []clickhouse.Disk, dstTable clickhouse.Table, skipProjections []string, logger zerolog.Logger) error {
1456+
func (b *Backuper) restoreDataRegularByParts(ctx context.Context, backupName string, backupMetadata metadata.BackupMetadata, table metadata.TableMetadata, diskMap, diskTypes map[string]string, disks []clickhouse.Disk, dstTable clickhouse.Table, skipProjections []string, logger zerolog.Logger, replicatedCopyToDetached bool) error {
14521457
if err := filesystemhelper.HardlinkBackupPartsToStorage(backupName, table, disks, diskMap, dstTable.DataPaths, skipProjections, b.ch, true); err != nil {
14531458
return fmt.Errorf("can't copy data to detached `%s`.`%s`: %v", table.Database, table.Table, err)
14541459
}
@@ -1461,8 +1466,13 @@ func (b *Backuper) restoreDataRegularByParts(ctx context.Context, backupName str
14611466
return fmt.Errorf("can't restore object_disk server-side copy data parts '%s.%s': %v", table.Database, table.Table, err)
14621467
}
14631468
log.Info().Str("duration", utils.HumanizeDuration(time.Since(start))).Str("size", utils.FormatBytes(uint64(size))).Msg("download object_disks finish")
1464-
if err := b.ch.AttachDataParts(table, dstTable, disks); err != nil {
1465-
return fmt.Errorf("can't attach data parts for table '%s.%s': %v", table.Database, table.Table, err)
1469+
// Skip ATTACH PART for Replicated*MergeTree tables if replicatedCopyToDetached is true
1470+
if !replicatedCopyToDetached || !strings.Contains(dstTable.Engine, "Replicated") {
1471+
if err := b.ch.AttachDataParts(table, dstTable, disks); err != nil {
1472+
return fmt.Errorf("can't attach data parts for table '%s.%s': %v", table.Database, table.Table, err)
1473+
}
1474+
} else {
1475+
logger.Info().Msg("skipping ATTACH PART for Replicated*MergeTree table due to --replicated-copy-to-detached flag")
14661476
}
14671477
return nil
14681478
}

pkg/backup/restore_remote.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ package backup
22

33
import "errors"
44

5-
func (b *Backuper) RestoreFromRemote(backupName, tablePattern string, databaseMapping, tableMapping, partitions, skipProjections []string, schemaOnly, dataOnly, dropExists, ignoreDependencies, restoreRBAC, rbacOnly, restoreConfigs, configsOnly, resume, schemaAsAttach bool, version string, commandId int) error {
5+
func (b *Backuper) RestoreFromRemote(backupName, tablePattern string, databaseMapping, tableMapping, partitions, skipProjections []string, schemaOnly, dataOnly, dropExists, ignoreDependencies, restoreRBAC, rbacOnly, restoreConfigs, configsOnly, resume, schemaAsAttach, replicatedCopyToDetached bool, version string, commandId int) error {
66
if err := b.Download(backupName, tablePattern, partitions, schemaOnly, rbacOnly, configsOnly, resume, version, commandId); err != nil {
77
// https://github.com/Altinity/clickhouse-backup/issues/625
88
if !errors.Is(err, ErrBackupIsAlreadyExists) {
99
return err
1010
}
1111
}
12-
return b.Restore(backupName, tablePattern, databaseMapping, tableMapping, partitions, skipProjections, schemaOnly, dataOnly, dropExists, ignoreDependencies, restoreRBAC, rbacOnly, restoreConfigs, configsOnly, resume, schemaAsAttach, version, commandId)
12+
return b.Restore(backupName, tablePattern, databaseMapping, tableMapping, partitions, skipProjections, schemaOnly, dataOnly, dropExists, ignoreDependencies, restoreRBAC, rbacOnly, restoreConfigs, configsOnly, resume, schemaAsAttach, replicatedCopyToDetached, version, commandId)
1313
}

pkg/server/server.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1295,6 +1295,7 @@ func (api *APIServer) httpRestoreHandler(w http.ResponseWriter, r *http.Request)
12951295
skipProjections := make([]string, 0)
12961296
resume := false
12971297
restoreSchemaAsAttach := false
1298+
replicatedCopyToDetached := false
12981299
fullCommand := "restore"
12991300
operationId, _ := uuid.NewUUID()
13001301

@@ -1414,6 +1415,19 @@ func (api *APIServer) httpRestoreHandler(w http.ResponseWriter, r *http.Request)
14141415
fullCommand += " --restore-schema-as-attach"
14151416
}
14161417
}
1418+
1419+
// Handle replicated-copy-to-detached parameter
1420+
replicatedCopyToDetachedParamName := "replicated_copy_to_detached"
1421+
replicatedCopyToDetachedParamNames := []string{
1422+
strings.Replace(replicatedCopyToDetachedParamName, "_", "-", -1),
1423+
strings.Replace(replicatedCopyToDetachedParamName, "-", "_", -1),
1424+
}
1425+
for _, paramName := range replicatedCopyToDetachedParamNames {
1426+
if _, exist := api.getQueryParameter(query, paramName); exist {
1427+
replicatedCopyToDetached = true
1428+
fullCommand += " --replicated-copy-to-detached"
1429+
}
1430+
}
14171431

14181432
name := utils.CleanBackupNameRE.ReplaceAllString(vars["name"], "")
14191433
fullCommand += fmt.Sprintf(" %s", name)
@@ -1429,7 +1443,7 @@ func (api *APIServer) httpRestoreHandler(w http.ResponseWriter, r *http.Request)
14291443
go func() {
14301444
err, _ := api.metrics.ExecuteWithMetrics("restore", 0, func() error {
14311445
b := backup.NewBackuper(api.config)
1432-
return b.Restore(name, tablePattern, databaseMappingToRestore, tableMappingToRestore, partitionsToBackup, skipProjections, schemaOnly, dataOnly, dropExists, ignoreDependencies, restoreRBAC, rbacOnly, restoreConfigs, configsOnly, resume, restoreSchemaAsAttach, api.cliApp.Version, commandId)
1446+
return b.Restore(name, tablePattern, databaseMappingToRestore, tableMappingToRestore, partitionsToBackup, skipProjections, schemaOnly, dataOnly, dropExists, ignoreDependencies, restoreRBAC, rbacOnly, restoreConfigs, configsOnly, resume, restoreSchemaAsAttach, replicatedCopyToDetached, api.cliApp.Version, commandId)
14331447
})
14341448
go func() {
14351449
if metricsErr := api.UpdateBackupMetrics(context.Background(), true); metricsErr != nil {

0 commit comments

Comments
 (0)