From 8dede490cb4c44a4b174a3bb754c4142daf34a9b Mon Sep 17 00:00:00 2001 From: Juncen Yu Date: Fri, 12 Jun 2026 08:54:39 +0000 Subject: [PATCH 1/5] added regionIndexStep to config --- br/pkg/restore/snap_client/client.go | 23 +++++++++++---- br/pkg/restore/snap_client/tikv_sender.go | 4 +-- br/pkg/restore/split/BUILD.bazel | 2 +- br/pkg/restore/split/split.go | 36 ++++++++++++++++++----- br/pkg/restore/split/split_test.go | 22 ++++++++++++++ br/pkg/task/BUILD.bazel | 3 +- br/pkg/task/common_test.go | 2 ++ br/pkg/task/config_test.go | 4 +++ br/pkg/task/restore.go | 16 ++++++++++ br/pkg/task/restore_nokit_test.go | 24 +++++++++++++++ 10 files changed, 119 insertions(+), 17 deletions(-) diff --git a/br/pkg/restore/snap_client/client.go b/br/pkg/restore/snap_client/client.go index 95584987b3139..1a3e5f789e09f 100644 --- a/br/pkg/restore/snap_client/client.go +++ b/br/pkg/restore/snap_client/client.go @@ -96,6 +96,7 @@ type SnapClient struct { cipher *backuppb.CipherInfo concurrencyPerStore uint regionScanConcurrency uint + splitRegionIndexStep uint keepaliveConf keepalive.ClientParameters rateLimit uint64 tlsConf *tls.Config @@ -186,11 +187,12 @@ func NewRestoreClient( keepaliveConf keepalive.ClientParameters, ) *SnapClient { return &SnapClient{ - pdClient: pdClient, - pdHTTPClient: pdHTTPCli, - tlsConf: tlsConf, - keepaliveConf: keepaliveConf, - switchCh: make(chan struct{}), + pdClient: pdClient, + pdHTTPClient: pdHTTPCli, + tlsConf: tlsConf, + keepaliveConf: keepaliveConf, + splitRegionIndexStep: split.DefaultRegionIndexStep, + switchCh: make(chan struct{}), } } @@ -317,6 +319,17 @@ func (rc *SnapClient) GetRegionScanConcurrency() uint { return rc.regionScanConcurrency } +// SetSplitRegionIndexStep sets the rough split step during snapshot restore. +func (rc *SnapClient) SetSplitRegionIndexStep(step uint) { + log.Info("split region index step", zap.Uint("step", step)) + rc.splitRegionIndexStep = split.NormalizeRegionIndexStep(step) +} + +// GetSplitRegionIndexStep returns the rough split step during snapshot restore. +func (rc *SnapClient) GetSplitRegionIndexStep() uint { + return rc.splitRegionIndexStep +} + func (rc *SnapClient) SetBatchDdlSize(batchDdlsize uint) { rc.batchDdlSize = batchDdlsize } diff --git a/br/pkg/restore/snap_client/tikv_sender.go b/br/pkg/restore/snap_client/tikv_sender.go index c5c83cddaa9ae..99b146e979219 100644 --- a/br/pkg/restore/snap_client/tikv_sender.go +++ b/br/pkg/restore/snap_client/tikv_sender.go @@ -367,14 +367,14 @@ func (rc *SnapClient) SplitPoints( splitClientOpts = append(splitClientOpts, split.WithRawKV()) } - splitter := split.NewRegionSplitter(split.NewClient( + splitter := split.NewRegionSplitterWithRegionIndexStep(split.NewClient( rc.pdClient, rc.pdHTTPClient, rc.tlsConf, maxSplitKeysOnce, rc.storeCount+1, splitClientOpts..., - )) + ), rc.splitRegionIndexStep) return splitter.ExecuteSortedKeys(ctx, sortedSplitKeys) } diff --git a/br/pkg/restore/split/BUILD.bazel b/br/pkg/restore/split/BUILD.bazel index 5c8526a2fe790..a4426aad1c6e3 100644 --- a/br/pkg/restore/split/BUILD.bazel +++ b/br/pkg/restore/split/BUILD.bazel @@ -67,7 +67,7 @@ go_test( ], embed = [":split"], flaky = True, - shard_count = 34, + shard_count = 35, deps = [ "//br/pkg/errors", "//br/pkg/restore/utils", diff --git a/br/pkg/restore/split/split.go b/br/pkg/restore/split/split.go index 9ff39b9a9d4c1..3fe2334722d61 100644 --- a/br/pkg/restore/split/split.go +++ b/br/pkg/restore/split/split.go @@ -36,17 +36,35 @@ const ( ScatterWaitUpperInterval = 30 * time.Minute ScanRegionPaginationLimit = 128 + + // DefaultRegionIndexStep is the default number of split-key indexes between two rough split keys. + DefaultRegionIndexStep uint = 128 ) +// NormalizeRegionIndexStep returns the default rough split step when the configured value is unset. +func NormalizeRegionIndexStep(regionIndexStep uint) uint { + if regionIndexStep == 0 { + return DefaultRegionIndexStep + } + return regionIndexStep +} + // RegionSplitter is a executor of region split by rules. type RegionSplitter struct { - client SplitClient + client SplitClient + regionIndexStep uint } // NewRegionSplitter returns a new RegionSplitter. func NewRegionSplitter(client SplitClient) *RegionSplitter { + return NewRegionSplitterWithRegionIndexStep(client, DefaultRegionIndexStep) +} + +// NewRegionSplitterWithRegionIndexStep returns a new RegionSplitter with the configured rough split step. +func NewRegionSplitterWithRegionIndexStep(client SplitClient, regionIndexStep uint) *RegionSplitter { return &RegionSplitter{ - client: client, + client: client, + regionIndexStep: NormalizeRegionIndexStep(regionIndexStep), } } @@ -80,12 +98,14 @@ func (rs *RegionSplitter) executeSplitByRanges( ) error { startTime := time.Now() // Choose the rough region split keys, - // each splited region contains 128 regions to be splitted. - const regionIndexStep = 128 - - roughSortedSplitKeys := make([][]byte, 0, len(sortedKeys)/regionIndexStep+1) - for curRegionIndex := regionIndexStep; curRegionIndex < len(sortedKeys); curRegionIndex += regionIndexStep { - roughSortedSplitKeys = append(roughSortedSplitKeys, sortedKeys[curRegionIndex]) + // each split region contains regionIndexStep regions to be split. + var roughSortedSplitKeys [][]byte + if rs.regionIndexStep < uint(len(sortedKeys)) { + regionIndexStep := int(rs.regionIndexStep) + roughSortedSplitKeys = make([][]byte, 0, len(sortedKeys)/regionIndexStep+1) + for curRegionIndex := regionIndexStep; curRegionIndex < len(sortedKeys); curRegionIndex += regionIndexStep { + roughSortedSplitKeys = append(roughSortedSplitKeys, sortedKeys[curRegionIndex]) + } } if len(roughSortedSplitKeys) > 0 { if err := rs.executeSplitByKeys(ctx, roughSortedSplitKeys); err != nil { diff --git a/br/pkg/restore/split/split_test.go b/br/pkg/restore/split/split_test.go index 342aa90d00190..205be28ca647e 100644 --- a/br/pkg/restore/split/split_test.go +++ b/br/pkg/restore/split/split_test.go @@ -1077,6 +1077,28 @@ func TestScanEmptyRegion(t *testing.T) { require.NoError(t, err) } +type recordingSplitClient struct { + SplitClient + splitCalls [][][]byte +} + +func (c *recordingSplitClient) SplitKeysAndScatter(_ context.Context, keys [][]byte) ([]*RegionInfo, error) { + c.splitCalls = append(c.splitCalls, slices.Clone(keys)) + return nil, nil +} + +func TestRegionSplitterRoughSplitUsesConfiguredRegionIndexStep(t *testing.T) { + client := &recordingSplitClient{} + keys := [][]byte{{'a'}, {'b'}, {'c'}, {'d'}, {'e'}, {'f'}} + regionSplitter := NewRegionSplitterWithRegionIndexStep(client, 2) + + err := regionSplitter.ExecuteSortedKeys(context.Background(), keys) + require.NoError(t, err) + require.Len(t, client.splitCalls, 2) + require.Equal(t, [][]byte{{'c'}, {'e'}}, client.splitCalls[0]) + require.Equal(t, keys, client.splitCalls[1]) +} + func TestSplitEmptyRegion(t *testing.T) { mockPDCli := NewMockPDClientForSplit() mockPDCli.SetRegions([][]byte{{}, {12}, {34}, {}}) diff --git a/br/pkg/task/BUILD.bazel b/br/pkg/task/BUILD.bazel index 7b09a59c3056e..c79ddc10c907c 100644 --- a/br/pkg/task/BUILD.bazel +++ b/br/pkg/task/BUILD.bazel @@ -40,6 +40,7 @@ go_library( "//br/pkg/restore/ingestrec", "//br/pkg/restore/log_client", "//br/pkg/restore/snap_client", + "//br/pkg/restore/split", "//br/pkg/restore/tiflashrec", "//br/pkg/restore/utils", "//br/pkg/rtree", @@ -126,7 +127,7 @@ go_test( ], embed = [":task"], flaky = True, - shard_count = 45, + shard_count = 46, deps = [ "//br/pkg/backup", "//br/pkg/config", diff --git a/br/pkg/task/common_test.go b/br/pkg/task/common_test.go index 7673adb8569e1..b7fa7cb6814d5 100644 --- a/br/pkg/task/common_test.go +++ b/br/pkg/task/common_test.go @@ -11,6 +11,7 @@ import ( kvconfig "github.com/pingcap/tidb/br/pkg/config" "github.com/pingcap/tidb/br/pkg/conn" "github.com/pingcap/tidb/br/pkg/gc" + restoresplit "github.com/pingcap/tidb/br/pkg/restore/split" "github.com/pingcap/tidb/pkg/config" "github.com/pingcap/tidb/pkg/objstore" "github.com/pingcap/tidb/pkg/objstore/s3like" @@ -323,6 +324,7 @@ func expectedDefaultRestoreConfig() RestoreConfig { BatchFlushInterval: 16000000000, DdlBatchSize: 0x80, RegionScanConcurrency: 256, + SplitRegionIndexStep: restoresplit.DefaultRegionIndexStep, WithPlacementPolicy: "STRICT", UseCheckpoint: true, AllowPITRFromIncremental: true, diff --git a/br/pkg/task/config_test.go b/br/pkg/task/config_test.go index 3c0ee7ed87830..3cc9fdf04007a 100644 --- a/br/pkg/task/config_test.go +++ b/br/pkg/task/config_test.go @@ -27,6 +27,7 @@ import ( "github.com/pingcap/tidb/br/pkg/conn" "github.com/pingcap/tidb/br/pkg/metautil" snapclient "github.com/pingcap/tidb/br/pkg/restore/snap_client" + restoresplit "github.com/pingcap/tidb/br/pkg/restore/split" "github.com/pingcap/tidb/br/pkg/utils" "github.com/pingcap/tidb/pkg/meta/model" "github.com/pingcap/tidb/pkg/objstore" @@ -48,6 +49,7 @@ func TestRestoreConfigAdjust(t *testing.T) { require.Equal(t, defaultSwitchInterval, cfg.Config.SwitchModeInterval) require.Equal(t, conn.DefaultMergeRegionKeyCount, cfg.MergeSmallRegionKeyCount.Value) require.Equal(t, conn.DefaultMergeRegionSizeBytes, cfg.MergeSmallRegionSizeBytes.Value) + require.Equal(t, restoresplit.DefaultRegionIndexStep, cfg.SplitRegionIndexStep) } type mockPDClient struct { @@ -78,6 +80,7 @@ func TestConfigureRestoreClient(t *testing.T) { RestoreCommonConfig: restoreComCfg, DdlBatchSize: 128, RegionScanConcurrency: 3, + SplitRegionIndexStep: 7, } client := snapclient.NewRestoreClient(mockPDClient{}, nil, nil, keepalive.ClientParameters{}) ctx := context.Background() @@ -85,6 +88,7 @@ func TestConfigureRestoreClient(t *testing.T) { require.NoError(t, err) require.Equal(t, uint(128), client.GetBatchDdlSize()) require.Equal(t, uint(3), client.GetRegionScanConcurrency()) + require.Equal(t, uint(7), client.GetSplitRegionIndexStep()) } func TestAdjustRestoreConfigForStreamRestore(t *testing.T) { diff --git a/br/pkg/task/restore.go b/br/pkg/task/restore.go index bf1996ab93329..192e40534eaa3 100644 --- a/br/pkg/task/restore.go +++ b/br/pkg/task/restore.go @@ -36,6 +36,7 @@ import ( "github.com/pingcap/tidb/br/pkg/registry" "github.com/pingcap/tidb/br/pkg/restore" snapclient "github.com/pingcap/tidb/br/pkg/restore/snap_client" + restoresplit "github.com/pingcap/tidb/br/pkg/restore/split" "github.com/pingcap/tidb/br/pkg/restore/tiflashrec" restoreutils "github.com/pingcap/tidb/br/pkg/restore/utils" "github.com/pingcap/tidb/br/pkg/stream" @@ -87,6 +88,8 @@ const ( FlagPDConcurrency = "pd-concurrency" // FlagRegionScanConcurrency controls max in-flight region scan requests to PD during restore. FlagRegionScanConcurrency = "region-scan-concurrency" + // FlagSplitRegionIndexStep controls the split-key index step used by rough split during snapshot restore. + FlagSplitRegionIndexStep = "split-region-index-step" // FlagStatsConcurrency controls concurrency to restore statistic. FlagStatsConcurrency = "stats-concurrency" // FlagBatchFlushInterval controls after how long the restore batch would be auto sended. @@ -271,6 +274,7 @@ type RestoreConfig struct { FastLoadSysTables bool `json:"fast-load-sys-tables" toml:"fast-load-sys-tables"` PDConcurrency uint `json:"pd-concurrency" toml:"pd-concurrency"` RegionScanConcurrency uint `json:"region-scan-concurrency" toml:"region-scan-concurrency"` + SplitRegionIndexStep uint `json:"split-region-index-step" toml:"split-region-index-step"` StatsConcurrency uint `json:"stats-concurrency" toml:"stats-concurrency"` BatchFlushInterval time.Duration `json:"batch-flush-interval" toml:"batch-flush-interval"` // DdlBatchSize use to define the size of batch ddl to create tables @@ -397,6 +401,9 @@ func DefineRestoreFlags(flags *pflag.FlagSet) { " these ddl jobs are Add index, Modify column and Reorganize partition") flags.Bool(FlagSysCheckCollation, false, "whether check the privileges table rows to permit to restore the privilege data"+ " from utf8mb4_bin collate column to utf8mb4_general_ci collate column") + flags.Uint(FlagSplitRegionIndexStep, restoresplit.DefaultRegionIndexStep, + "number of split-key indexes between two rough split keys during snapshot restore.") + _ = flags.MarkHidden(FlagSplitRegionIndexStep) DefineRestoreCommonFlags(flags) } @@ -498,6 +505,13 @@ func (cfg *RestoreConfig) ParseFromFlags(flags *pflag.FlagSet, skipCommonConfig if err != nil { return errors.Annotatef(err, "failed to get flag %s", FlagRegionScanConcurrency) } + cfg.SplitRegionIndexStep, err = flags.GetUint(FlagSplitRegionIndexStep) + if err != nil { + return errors.Annotatef(err, "failed to get flag %s", FlagSplitRegionIndexStep) + } + if cfg.SplitRegionIndexStep == 0 { + return errors.Annotatef(berrors.ErrInvalidArgument, "%s must be greater than 0", FlagSplitRegionIndexStep) + } cfg.StatsConcurrency, err = flags.GetUint(FlagStatsConcurrency) if err != nil { return errors.Annotatef(err, "failed to get flag %s", FlagStatsConcurrency) @@ -649,6 +663,7 @@ func (cfg *RestoreConfig) Adjust() { if cfg.PDConcurrency == 0 { cfg.PDConcurrency = defaultPDConcurrency } + cfg.SplitRegionIndexStep = restoresplit.NormalizeRegionIndexStep(cfg.SplitRegionIndexStep) if cfg.StatsConcurrency == 0 { cfg.StatsConcurrency = defaultStatsConcurrency } @@ -790,6 +805,7 @@ func configureRestoreClient(ctx context.Context, client *snapclient.SnapClient, client.SetRateLimit(cfg.RateLimit) client.SetCrypter(&cfg.CipherInfo) client.SetRegionScanConcurrency(cfg.RegionScanConcurrency) + client.SetSplitRegionIndexStep(cfg.SplitRegionIndexStep) if cfg.NoSchema { client.EnableSkipCreateSQL() } diff --git a/br/pkg/task/restore_nokit_test.go b/br/pkg/task/restore_nokit_test.go index 0e947b41a1ea5..b4b5bb7bdfd0c 100644 --- a/br/pkg/task/restore_nokit_test.go +++ b/br/pkg/task/restore_nokit_test.go @@ -53,3 +53,27 @@ func TestRestorePhaseRequiresCheckpoint(t *testing.T) { require.ErrorContains(t, err, FlagRestorePhase) require.ErrorContains(t, err, flagUseCheckpoint) } + +func TestSplitRegionIndexStepFlag(t *testing.T) { + t.Run("custom", func(t *testing.T) { + flags := pflag.NewFlagSet("restore", pflag.ContinueOnError) + DefineRestoreFlags(flags) + require.NoError(t, flags.Set(FlagSplitRegionIndexStep, "64")) + + cfg := &RestoreConfig{} + require.NoError(t, cfg.ParseFromFlags(flags, true)) + require.Equal(t, uint(64), cfg.SplitRegionIndexStep) + }) + + t.Run("zero", func(t *testing.T) { + flags := pflag.NewFlagSet("restore", pflag.ContinueOnError) + DefineRestoreFlags(flags) + require.NoError(t, flags.Set(FlagSplitRegionIndexStep, "0")) + + cfg := &RestoreConfig{} + err := cfg.ParseFromFlags(flags, true) + require.Error(t, err) + require.ErrorContains(t, err, FlagSplitRegionIndexStep) + require.ErrorContains(t, err, "greater than 0") + }) +} From b76008d9c0966e06019bb564e588fe919dd17b07 Mon Sep 17 00:00:00 2001 From: Juncen Yu Date: Fri, 12 Jun 2026 09:32:22 +0000 Subject: [PATCH 2/5] br: add coarse scatter restore option --- br/pkg/restore/snap_client/client.go | 12 + br/pkg/restore/snap_client/tikv_sender.go | 1 + br/pkg/restore/split/client.go | 38 ++- br/pkg/restore/split/client_test.go | 18 +- br/pkg/restore/split/split.go | 24 +- br/pkg/restore/split/split_test.go | 46 +++- br/pkg/task/config_test.go | 3 + br/pkg/task/restore.go | 9 + br/pkg/task/restore_nokit_test.go | 10 + br_coarse_scatter_playground_verification.md | 272 +++++++++++++++++++ 10 files changed, 408 insertions(+), 25 deletions(-) create mode 100644 br_coarse_scatter_playground_verification.md diff --git a/br/pkg/restore/snap_client/client.go b/br/pkg/restore/snap_client/client.go index 1a3e5f789e09f..28ab362efd9b2 100644 --- a/br/pkg/restore/snap_client/client.go +++ b/br/pkg/restore/snap_client/client.go @@ -97,6 +97,7 @@ type SnapClient struct { concurrencyPerStore uint regionScanConcurrency uint splitRegionIndexStep uint + coarseScatter bool keepaliveConf keepalive.ClientParameters rateLimit uint64 tlsConf *tls.Config @@ -330,6 +331,17 @@ func (rc *SnapClient) GetSplitRegionIndexStep() uint { return rc.splitRegionIndexStep } +// SetCoarseScatter controls whether only rough split regions are scattered. +func (rc *SnapClient) SetCoarseScatter(coarseScatter bool) { + log.Info("coarse scatter", zap.Bool("enabled", coarseScatter)) + rc.coarseScatter = coarseScatter +} + +// GetCoarseScatter returns whether only rough split regions are scattered. +func (rc *SnapClient) GetCoarseScatter() bool { + return rc.coarseScatter +} + func (rc *SnapClient) SetBatchDdlSize(batchDdlsize uint) { rc.batchDdlSize = batchDdlsize } diff --git a/br/pkg/restore/snap_client/tikv_sender.go b/br/pkg/restore/snap_client/tikv_sender.go index 99b146e979219..01073e653fe28 100644 --- a/br/pkg/restore/snap_client/tikv_sender.go +++ b/br/pkg/restore/snap_client/tikv_sender.go @@ -375,6 +375,7 @@ func (rc *SnapClient) SplitPoints( rc.storeCount+1, splitClientOpts..., ), rc.splitRegionIndexStep) + splitter.SetCoarseScatter(rc.coarseScatter) return splitter.ExecuteSortedKeys(ctx, sortedSplitKeys) } diff --git a/br/pkg/restore/split/client.go b/br/pkg/restore/split/client.go index 6b646ddb228ff..c25225a32762b 100644 --- a/br/pkg/restore/split/client.go +++ b/br/pkg/restore/split/client.go @@ -123,6 +123,10 @@ type SplitClient interface { GetCodecPDClient() *tikvclient.CodecPDClient } +type splitClientWithScatterControl interface { + SplitKeys(ctx context.Context, sortedSplitKeys [][]byte, scatter bool) ([]*RegionInfo, error) +} + // pdClient is a wrapper of pd client, can be used by RegionSplitter. type pdClient struct { mu sync.Mutex @@ -627,6 +631,10 @@ func (c *pdClient) getEncodedKeys(start, end []byte) (encodedStart, encodedEnd [ } func (c *pdClient) SplitKeysAndScatter(ctx context.Context, sortedSplitKeys [][]byte) ([]*RegionInfo, error) { + return c.SplitKeys(ctx, sortedSplitKeys, true) +} + +func (c *pdClient) SplitKeys(ctx context.Context, sortedSplitKeys [][]byte, scatter bool) ([]*RegionInfo, error) { if len(sortedSplitKeys) == 0 { return nil, nil } @@ -686,7 +694,7 @@ func (c *pdClient) SplitKeysAndScatter(ctx context.Context, sortedSplitKeys [][] for region, splitKeys := range splitKeyMap { workerPool.ApplyOnErrorGroup(eg, func() error { // TODO(lance6716): add error handling to retry from scan or retry from split - newRegions, err2 := c.SplitWaitAndScatter(eCtx, region, splitKeys) + newRegions, err2 := c.splitWaitAndMaybeScatter(eCtx, region, splitKeys, scatter) if err2 != nil { if common.IsContextCanceledError(err2) { return err2 @@ -701,14 +709,16 @@ func (c *pdClient) SplitKeysAndScatter(ctx context.Context, sortedSplitKeys [][] return nil } - if len(newRegions) != len(splitKeys) { + if scatter && len(newRegions) != len(splitKeys) { log.Warn("split key count and new region count mismatch", zap.Int("new region count", len(newRegions)), zap.Int("split key count", len(splitKeys))) } - mu.Lock() - ret = append(ret, newRegions...) - mu.Unlock() + if scatter { + mu.Lock() + ret = append(ret, newRegions...) + mu.Unlock() + } return nil }) } @@ -729,6 +739,10 @@ func isNonRetryErrForSplit(err error) bool { } func (c *pdClient) SplitWaitAndScatter(ctx context.Context, region *RegionInfo, keys [][]byte) ([]*RegionInfo, error) { + return c.splitWaitAndMaybeScatter(ctx, region, keys, true) +} + +func (c *pdClient) splitWaitAndMaybeScatter(ctx context.Context, region *RegionInfo, keys [][]byte, scatter bool) ([]*RegionInfo, error) { failpoint.Inject("failToSplit", func(_ failpoint.Value) { failpoint.Return(nil, errors.New("retryable error")) }) @@ -761,12 +775,14 @@ func (c *pdClient) SplitWaitAndScatter(ctx context.Context, region *RegionInfo, if err = ctx.Err(); err != nil { return nil, errors.Trace(err) } - err = c.scatterRegions(ctx, newRegionsOfBatch) - if err != nil { - tidblogutil.Logger(ctx).Warn( - "scatter regions failed, will continue anyway", - zap.Error(err), - ) + if scatter { + err = c.scatterRegions(ctx, newRegionsOfBatch) + if err != nil { + tidblogutil.Logger(ctx).Warn( + "scatter regions failed, will continue anyway", + zap.Error(err), + ) + } } if c.onSplit != nil { c.onSplit(keys[start:end]) diff --git a/br/pkg/restore/split/client_test.go b/br/pkg/restore/split/client_test.go index fd3437e71b9c9..17323ccef39bb 100644 --- a/br/pkg/restore/split/client_test.go +++ b/br/pkg/restore/split/client_test.go @@ -119,7 +119,23 @@ func TestSplitScatter(t *testing.T) { } } - _, err := mockClient.SplitKeysAndScatter(ctx, splitKeys) + noScatterPDClient := NewMockPDClientForSplit() + noScatterPDClient.SetRegions(keys) + noScatterClient := &pdClient{ + client: noScatterPDClient, + splitConcurrency: 20, + splitBatchKeyCnt: 100, + } + noScatterSplitKeys := make([][]byte, 0, len(splitKeys)) + for _, key := range splitKeys { + noScatterSplitKeys = append(noScatterSplitKeys, append([]byte(nil), key...)) + } + _, err := noScatterClient.SplitKeys(ctx, noScatterSplitKeys, false) + require.NoError(t, err) + require.Positive(t, noScatterPDClient.splitRegions.count) + require.Zero(t, noScatterPDClient.scatterRegions.regionCount) + + _, err = mockClient.SplitKeysAndScatter(ctx, splitKeys) require.NoError(t, err) // check split ranges diff --git a/br/pkg/restore/split/split.go b/br/pkg/restore/split/split.go index 3fe2334722d61..0be18d9149915 100644 --- a/br/pkg/restore/split/split.go +++ b/br/pkg/restore/split/split.go @@ -53,6 +53,7 @@ func NormalizeRegionIndexStep(regionIndexStep uint) uint { type RegionSplitter struct { client SplitClient regionIndexStep uint + coarseScatter bool } // NewRegionSplitter returns a new RegionSplitter. @@ -68,6 +69,11 @@ func NewRegionSplitterWithRegionIndexStep(client SplitClient, regionIndexStep ui } } +// SetCoarseScatter makes only rough split regions scattered. +func (rs *RegionSplitter) SetCoarseScatter(coarseScatter bool) { + rs.coarseScatter = coarseScatter +} + // ExecuteSortedKeysOnRegion expose the function `SplitWaitAndScatter` of split client. func (rs *RegionSplitter) ExecuteSortedKeysOnRegion(ctx context.Context, region *RegionInfo, keys [][]byte) ([]*RegionInfo, error) { return rs.client.SplitWaitAndScatter(ctx, region, keys) @@ -108,14 +114,14 @@ func (rs *RegionSplitter) executeSplitByRanges( } } if len(roughSortedSplitKeys) > 0 { - if err := rs.executeSplitByKeys(ctx, roughSortedSplitKeys); err != nil { + if err := rs.executeSplitByKeys(ctx, roughSortedSplitKeys, true); err != nil { return errors.Trace(err) } } log.Info("finish spliting regions roughly", zap.Duration("take", time.Since(startTime))) // Then send split requests to each TiKV. - if err := rs.executeSplitByKeys(ctx, sortedKeys); err != nil { + if err := rs.executeSplitByKeys(ctx, sortedKeys, !rs.coarseScatter); err != nil { return errors.Trace(err) } @@ -130,9 +136,10 @@ func (rs *RegionSplitter) executeSplitByRanges( func (rs *RegionSplitter) executeSplitByKeys( ctx context.Context, sortedKeys [][]byte, + scatter bool, ) error { startTime := time.Now() - scatterRegions, err := rs.client.SplitKeysAndScatter(ctx, sortedKeys) + scatterRegions, err := rs.splitKeys(ctx, sortedKeys, scatter) if err != nil { return errors.Trace(err) } @@ -146,6 +153,17 @@ func (rs *RegionSplitter) executeSplitByKeys( return nil } +func (rs *RegionSplitter) splitKeys(ctx context.Context, sortedKeys [][]byte, scatter bool) ([]*RegionInfo, error) { + if scatter { + return rs.client.SplitKeysAndScatter(ctx, sortedKeys) + } + client, ok := rs.client.(splitClientWithScatterControl) + if !ok { + return nil, errors.New("split client does not support split without scatter") + } + return client.SplitKeys(ctx, sortedKeys, false) +} + // waitRegionsScattered try to wait mutilple regions scatterd in 3 minutes. // this could timeout, but if many regions scatterd the restore could continue // so we don't wait long time here. diff --git a/br/pkg/restore/split/split_test.go b/br/pkg/restore/split/split_test.go index 205be28ca647e..8b8ae7f9442a9 100644 --- a/br/pkg/restore/split/split_test.go +++ b/br/pkg/restore/split/split_test.go @@ -1079,24 +1079,50 @@ func TestScanEmptyRegion(t *testing.T) { type recordingSplitClient struct { SplitClient - splitCalls [][][]byte + splitCalls [][][]byte + scatterByCalls []bool } -func (c *recordingSplitClient) SplitKeysAndScatter(_ context.Context, keys [][]byte) ([]*RegionInfo, error) { +func (c *recordingSplitClient) SplitKeysAndScatter(ctx context.Context, keys [][]byte) ([]*RegionInfo, error) { + return c.SplitKeys(ctx, keys, true) +} + +func (c *recordingSplitClient) SplitKeys(_ context.Context, keys [][]byte, scatter bool) ([]*RegionInfo, error) { c.splitCalls = append(c.splitCalls, slices.Clone(keys)) + c.scatterByCalls = append(c.scatterByCalls, scatter) return nil, nil } func TestRegionSplitterRoughSplitUsesConfiguredRegionIndexStep(t *testing.T) { - client := &recordingSplitClient{} keys := [][]byte{{'a'}, {'b'}, {'c'}, {'d'}, {'e'}, {'f'}} - regionSplitter := NewRegionSplitterWithRegionIndexStep(client, 2) - - err := regionSplitter.ExecuteSortedKeys(context.Background(), keys) - require.NoError(t, err) - require.Len(t, client.splitCalls, 2) - require.Equal(t, [][]byte{{'c'}, {'e'}}, client.splitCalls[0]) - require.Equal(t, keys, client.splitCalls[1]) + for _, testCase := range []struct { + name string + coarseScatter bool + scatterByCalls []bool + }{ + { + name: "default scatter", + scatterByCalls: []bool{true, true}, + }, + { + name: "coarse scatter", + coarseScatter: true, + scatterByCalls: []bool{true, false}, + }, + } { + t.Run(testCase.name, func(t *testing.T) { + client := &recordingSplitClient{} + regionSplitter := NewRegionSplitterWithRegionIndexStep(client, 2) + regionSplitter.SetCoarseScatter(testCase.coarseScatter) + + err := regionSplitter.ExecuteSortedKeys(context.Background(), keys) + require.NoError(t, err) + require.Len(t, client.splitCalls, 2) + require.Equal(t, [][]byte{{'c'}, {'e'}}, client.splitCalls[0]) + require.Equal(t, keys, client.splitCalls[1]) + require.Equal(t, testCase.scatterByCalls, client.scatterByCalls) + }) + } } func TestSplitEmptyRegion(t *testing.T) { diff --git a/br/pkg/task/config_test.go b/br/pkg/task/config_test.go index 3cc9fdf04007a..355bd88f27378 100644 --- a/br/pkg/task/config_test.go +++ b/br/pkg/task/config_test.go @@ -50,6 +50,7 @@ func TestRestoreConfigAdjust(t *testing.T) { require.Equal(t, conn.DefaultMergeRegionKeyCount, cfg.MergeSmallRegionKeyCount.Value) require.Equal(t, conn.DefaultMergeRegionSizeBytes, cfg.MergeSmallRegionSizeBytes.Value) require.Equal(t, restoresplit.DefaultRegionIndexStep, cfg.SplitRegionIndexStep) + require.False(t, cfg.CoarseScatter) } type mockPDClient struct { @@ -81,6 +82,7 @@ func TestConfigureRestoreClient(t *testing.T) { DdlBatchSize: 128, RegionScanConcurrency: 3, SplitRegionIndexStep: 7, + CoarseScatter: true, } client := snapclient.NewRestoreClient(mockPDClient{}, nil, nil, keepalive.ClientParameters{}) ctx := context.Background() @@ -89,6 +91,7 @@ func TestConfigureRestoreClient(t *testing.T) { require.Equal(t, uint(128), client.GetBatchDdlSize()) require.Equal(t, uint(3), client.GetRegionScanConcurrency()) require.Equal(t, uint(7), client.GetSplitRegionIndexStep()) + require.True(t, client.GetCoarseScatter()) } func TestAdjustRestoreConfigForStreamRestore(t *testing.T) { diff --git a/br/pkg/task/restore.go b/br/pkg/task/restore.go index 192e40534eaa3..fe890149e9d8b 100644 --- a/br/pkg/task/restore.go +++ b/br/pkg/task/restore.go @@ -90,6 +90,8 @@ const ( FlagRegionScanConcurrency = "region-scan-concurrency" // FlagSplitRegionIndexStep controls the split-key index step used by rough split during snapshot restore. FlagSplitRegionIndexStep = "split-region-index-step" + // FlagCoarseScatter controls whether only rough split regions are scattered during snapshot restore. + FlagCoarseScatter = "corase-scatter" // FlagStatsConcurrency controls concurrency to restore statistic. FlagStatsConcurrency = "stats-concurrency" // FlagBatchFlushInterval controls after how long the restore batch would be auto sended. @@ -275,6 +277,7 @@ type RestoreConfig struct { PDConcurrency uint `json:"pd-concurrency" toml:"pd-concurrency"` RegionScanConcurrency uint `json:"region-scan-concurrency" toml:"region-scan-concurrency"` SplitRegionIndexStep uint `json:"split-region-index-step" toml:"split-region-index-step"` + CoarseScatter bool `json:"corase-scatter" toml:"corase-scatter"` StatsConcurrency uint `json:"stats-concurrency" toml:"stats-concurrency"` BatchFlushInterval time.Duration `json:"batch-flush-interval" toml:"batch-flush-interval"` // DdlBatchSize use to define the size of batch ddl to create tables @@ -404,6 +407,7 @@ func DefineRestoreFlags(flags *pflag.FlagSet) { flags.Uint(FlagSplitRegionIndexStep, restoresplit.DefaultRegionIndexStep, "number of split-key indexes between two rough split keys during snapshot restore.") _ = flags.MarkHidden(FlagSplitRegionIndexStep) + flags.Bool(FlagCoarseScatter, false, "only scatter regions from rough split during snapshot restore") DefineRestoreCommonFlags(flags) } @@ -512,6 +516,10 @@ func (cfg *RestoreConfig) ParseFromFlags(flags *pflag.FlagSet, skipCommonConfig if cfg.SplitRegionIndexStep == 0 { return errors.Annotatef(berrors.ErrInvalidArgument, "%s must be greater than 0", FlagSplitRegionIndexStep) } + cfg.CoarseScatter, err = flags.GetBool(FlagCoarseScatter) + if err != nil { + return errors.Annotatef(err, "failed to get flag %s", FlagCoarseScatter) + } cfg.StatsConcurrency, err = flags.GetUint(FlagStatsConcurrency) if err != nil { return errors.Annotatef(err, "failed to get flag %s", FlagStatsConcurrency) @@ -806,6 +814,7 @@ func configureRestoreClient(ctx context.Context, client *snapclient.SnapClient, client.SetCrypter(&cfg.CipherInfo) client.SetRegionScanConcurrency(cfg.RegionScanConcurrency) client.SetSplitRegionIndexStep(cfg.SplitRegionIndexStep) + client.SetCoarseScatter(cfg.CoarseScatter) if cfg.NoSchema { client.EnableSkipCreateSQL() } diff --git a/br/pkg/task/restore_nokit_test.go b/br/pkg/task/restore_nokit_test.go index b4b5bb7bdfd0c..51bd09cf9dca5 100644 --- a/br/pkg/task/restore_nokit_test.go +++ b/br/pkg/task/restore_nokit_test.go @@ -55,6 +55,16 @@ func TestRestorePhaseRequiresCheckpoint(t *testing.T) { } func TestSplitRegionIndexStepFlag(t *testing.T) { + t.Run("coarse scatter", func(t *testing.T) { + flags := pflag.NewFlagSet("restore", pflag.ContinueOnError) + DefineRestoreFlags(flags) + require.NoError(t, flags.Set(FlagCoarseScatter, "true")) + + cfg := &RestoreConfig{} + require.NoError(t, cfg.ParseFromFlags(flags, true)) + require.True(t, cfg.CoarseScatter) + }) + t.Run("custom", func(t *testing.T) { flags := pflag.NewFlagSet("restore", pflag.ContinueOnError) DefineRestoreFlags(flags) diff --git a/br_coarse_scatter_playground_verification.md b/br_coarse_scatter_playground_verification.md new file mode 100644 index 0000000000000..f0c6f77a989af --- /dev/null +++ b/br_coarse_scatter_playground_verification.md @@ -0,0 +1,272 @@ +# BR coarse scatter playground verification + +Date: 2026-06-12 UTC + +Purpose: verify with a real TiUP playground that `br restore ... --corase-scatter=true` only scatters rough split regions. + +## Step 0. Build the local BR binary + +Command: + +```bash +go build -o /tmp/br-coarse-scatter ./br/cmd/br +/tmp/br-coarse-scatter --version +/tmp/br-coarse-scatter restore db --help | grep -- --corase-scatter +``` + +Output: + +```text +Release Version: nightly-dirty +Git Commit Hash: None +Git Branch: None +Go Version: go1.25.10 +UTC Build Time: None +Race Enabled: false +Kernel Type: Classic + --corase-scatter only scatter regions from rough split during snapshot restore +``` + +Note: I first tried `tiup playground v8.5.6`, but the locally built BR uses a newer PD client and backup repeatedly failed with `rpc error: code = Unimplemented desc = unknown method QueryRegion for service pdpb.PD`. The successful verification below uses the already installed `v9.0.0-beta.2.pre-nightly` playground components. + +## Step 1. Start playground + +Command: + +```bash +tiup playground v9.0.0-beta.2.pre-nightly --db 1 --pd 1 --kv 3 --tiflash 0 --without-monitor --tag br-coarse-scatter-verify +curl -sf http://127.0.0.1:2379/pd/api/v1/version +mysql -h 127.0.0.1 -P 4000 -u root -e 'select version();' +curl -s http://127.0.0.1:2379/pd/api/v1/config/replicate +``` + +Output: + +```text +playground pid: 2367090 +{ + "version": "v9.0.0-beta.2.pre-335-g5e71b3a", + "build_time": "", + "hash": "", + "branch": "" +} +version() +8.0.11-TiDB-v9.0.0-beta.2.pre-1504-ga5ad421c49 +up stores: 3 +{ + "max-replicas": 3, + "location-labels": "", + "strictly-match-label": "false", + "enable-placement-rules": "true", + "enable-placement-rules-cache": "false", + "isolation-level": "" +} +``` + +Why this matters: BR only performs scatter when `store-count >= max-replicas` and `store-count > 1`. This playground has 3 Up stores and `max-replicas = 3`, so scatter is active. + +## Step 2. Create split source databases + +Command: + +```sql +DROP DATABASE IF EXISTS br_cs_default; +DROP DATABASE IF EXISTS br_cs_coarse; +CREATE DATABASE br_cs_default; +CREATE DATABASE br_cs_coarse; +CREATE TABLE br_cs_default.t (id BIGINT PRIMARY KEY, v VARCHAR(32)); +CREATE TABLE br_cs_coarse.t (id BIGINT PRIMARY KEY, v VARCHAR(32)); +SPLIT TABLE br_cs_default.t BETWEEN (0) AND (80000) REGIONS 8; +SPLIT TABLE br_cs_coarse.t BETWEEN (0) AND (80000) REGIONS 8; +INSERT INTO br_cs_default.t VALUES (5000,'v5000'),(15000,'v15000'),(25000,'v25000'),(35000,'v35000'),(45000,'v45000'),(55000,'v55000'),(65000,'v65000'),(75000,'v75000'); +INSERT INTO br_cs_coarse.t VALUES (5000,'v5000'),(15000,'v15000'),(25000,'v25000'),(35000,'v35000'),(45000,'v45000'),(55000,'v55000'),(65000,'v65000'),(75000,'v75000'); +SELECT 'br_cs_default' AS db_name, COUNT(*) AS row_count FROM br_cs_default.t; +SELECT 'br_cs_coarse' AS db_name, COUNT(*) AS row_count FROM br_cs_coarse.t; +``` + +Output: + +```text +SPLIT TABLE br_cs_default.t BETWEEN (0) AND (80000) REGIONS 8 ++--------------------+----------------------+ +| TOTAL_SPLIT_REGION | SCATTER_FINISH_RATIO | ++--------------------+----------------------+ +| 7 | 1 | ++--------------------+----------------------+ + +SPLIT TABLE br_cs_coarse.t BETWEEN (0) AND (80000) REGIONS 8 ++--------------------+----------------------+ +| TOTAL_SPLIT_REGION | SCATTER_FINISH_RATIO | ++--------------------+----------------------+ +| 7 | 1 | ++--------------------+----------------------+ + +INSERT INTO br_cs_default.t ... +Query OK, 8 rows affected + +INSERT INTO br_cs_coarse.t ... +Query OK, 8 rows affected + ++---------------+-----------+ +| db_name | row_count | ++---------------+-----------+ +| br_cs_default | 8 | ++---------------+-----------+ + ++--------------+-----------+ +| db_name | row_count | ++--------------+-----------+ +| br_cs_coarse | 8 | ++--------------+-----------+ +``` + +## Step 3. Back up both databases + +Command: + +```bash +/tmp/br-coarse-scatter backup db --db br_cs_default --pd 127.0.0.1:2379 -s local:///tmp/br-coarse-scatter-verify/backup-default --log-file /tmp/br-coarse-scatter-verify/backup-default.log --checksum=false --check-requirements=false +/tmp/br-coarse-scatter backup db --db br_cs_coarse --pd 127.0.0.1:2379 -s local:///tmp/br-coarse-scatter-verify/backup-coarse --log-file /tmp/br-coarse-scatter-verify/backup-coarse.log --checksum=false --check-requirements=false +find /tmp/br-coarse-scatter-verify/backup-default -maxdepth 1 -type f | wc -l +find /tmp/br-coarse-scatter-verify/backup-coarse -maxdepth 1 -type f | wc -l +``` + +Output: + +```text +Detail BR log in /tmp/br-coarse-scatter-verify/backup-default.log +[2026/06/12 09:19:21.804 +00:00] [INFO] [collector.go:77] ["Database Backup success summary"] [total-ranges=8] [ranges-succeed=8] [ranges-failed=0] [backup-checksum=3.717806ms] [write-CF-files=8] [backup-total-ranges=1] [backup-total-regions=8] [total-take=3.493531474s] [backup-data-size(after-compressed)=14.08kB] [Size=14079] [BackupTS=466945561939935258] [total-kv=8] [total-kv-size=271B] [average-speed=77.57B/s] +Detail BR log in /tmp/br-coarse-scatter-verify/backup-coarse.log +[2026/06/12 09:19:25.329 +00:00] [INFO] [collector.go:77] ["Database Backup success summary"] [total-ranges=8] [ranges-succeed=8] [ranges-failed=0] [backup-checksum=1.6606ms] [backup-total-regions=8] [write-CF-files=8] [backup-total-ranges=1] [total-take=3.479387406s] [BackupTS=466945562870546436] [total-kv=8] [total-kv-size=271B] [average-speed=77.89B/s] [backup-data-size(after-compressed)=14.08kB] [Size=14079] +backup-default files: 4 +backup-coarse files: 4 +``` + +## Step 4. Drop source databases before restore + +Command: + +```sql +DROP DATABASE br_cs_default; +DROP DATABASE br_cs_coarse; +SHOW DATABASES LIKE 'br_cs_%'; +``` + +Output: + +```text +DROP DATABASE br_cs_default +Query OK, 0 rows affected + +DROP DATABASE br_cs_coarse +Query OK, 0 rows affected + +SHOW DATABASES LIKE 'br_cs_%' +Empty set +``` + +## Step 5. Restore with default scatter behavior + +Command: + +```bash +/tmp/br-coarse-scatter restore db --db br_cs_default --pd 127.0.0.1:2379 -s local:///tmp/br-coarse-scatter-verify/backup-default --log-file /tmp/br-coarse-scatter-verify/restore-default.log --checksum=false --check-requirements=false --load-stats=false --split-region-index-step=2 --merge-region-size-bytes=1 --merge-region-key-count=1 +mysql -h 127.0.0.1 -P 4000 -u root -e "SELECT COUNT(*) AS default_rows FROM br_cs_default.t;" +``` + +Output: + +```text +Detail BR log in /tmp/br-coarse-scatter-verify/restore-default.log +[2026/06/12 09:19:28.754 +00:00] [INFO] [collector.go:77] ["DataBase Restore success summary"] [total-ranges=8] [ranges-succeed=8] [ranges-failed=0] [merge-ranges=491.94us] [split-regions=41.509104ms] [restore-files=16.351669ms] [restore-pipeline=63.655us] [default-CF-files=0] [write-CF-files=8] [split-keys=8] [total-take=3.266305201s] [restore-data-size(after-compressed)=14.08kB] [Size=14079] [BackupTS=466945561939935258] [RestoreTS=466945563840479237] [total-kv=8] [total-kv-size=271B] [average-speed=82.97B/s] +default_rows +8 +``` + +## Step 6. Restore with coarse scatter enabled + +Command: + +```bash +/tmp/br-coarse-scatter restore db --db br_cs_coarse --pd 127.0.0.1:2379 -s local:///tmp/br-coarse-scatter-verify/backup-coarse --log-file /tmp/br-coarse-scatter-verify/restore-coarse.log --checksum=false --check-requirements=false --load-stats=false --split-region-index-step=2 --merge-region-size-bytes=1 --merge-region-key-count=1 --corase-scatter=true +mysql -h 127.0.0.1 -P 4000 -u root -e "SELECT COUNT(*) AS coarse_rows FROM br_cs_coarse.t;" +``` + +Output: + +```text +Detail BR log in /tmp/br-coarse-scatter-verify/restore-coarse.log +[2026/06/12 09:19:34.109 +00:00] [INFO] [collector.go:77] ["DataBase Restore success summary"] [total-ranges=8] [ranges-succeed=8] [ranges-failed=0] [merge-ranges=151.758us] [split-regions=43.057546ms] [restore-files=6.990495ms] [restore-pipeline=78.206us] [split-keys=8] [default-CF-files=0] [write-CF-files=8] [total-take=3.254340464s] [Size=14079] [BackupTS=466945562870546436] [RestoreTS=466945565242949700] [total-kv=8] [total-kv-size=271B] [average-speed=83.27B/s] [restore-data-size(after-compressed)=14.08kB] +coarse_rows +8 +``` + +## Step 7. Proof from BR restore logs + +Command: + +```bash +grep -c '"scatter regions"' /tmp/br-coarse-scatter-verify/restore-default.log +grep -c '"scatter regions"' /tmp/br-coarse-scatter-verify/restore-coarse.log +grep -nE 'coarse scatter|execute split sorted keys|"scatter regions"|finish spliting regions roughly|finish spliting and scattering regions|finish splitting regions\.' /tmp/br-coarse-scatter-verify/restore-default.log +grep -nE 'coarse scatter|execute split sorted keys|"scatter regions"|finish spliting regions roughly|finish spliting and scattering regions|finish splitting regions\.' /tmp/br-coarse-scatter-verify/restore-coarse.log +``` + +Output: + +```text +default restore scatter log count: 5 +coarse restore scatter log count: 1 + +-- default log key lines -- +92:[2026/06/12 09:19:25.550 +00:00] [INFO] [client.go:336] ["coarse scatter"] [enabled=false] +181:[2026/06/12 09:19:28.675 +00:00] [INFO] [split.go:97] ["execute split sorted keys"] ["keys count"=8] +184:[2026/06/12 09:19:28.690 +00:00] [INFO] [client.go:240] ["scatter regions"] [regions=3] +189:[2026/06/12 09:19:28.702 +00:00] [INFO] [split.go:121] ["finish spliting regions roughly"] [take=26.271794ms] +192:[2026/06/12 09:19:28.714 +00:00] [INFO] [client.go:240] ["scatter regions"] [regions=1] +193:[2026/06/12 09:19:28.714 +00:00] [INFO] [client.go:240] ["scatter regions"] [regions=1] +194:[2026/06/12 09:19:28.715 +00:00] [INFO] [client.go:240] ["scatter regions"] [regions=1] +195:[2026/06/12 09:19:28.715 +00:00] [INFO] [client.go:240] ["scatter regions"] [regions=2] +199:[2026/06/12 09:19:28.717 +00:00] [INFO] [split.go:128] ["finish spliting and scattering regions"] [take=41.467275ms] + +-- coarse log key lines -- +92:[2026/06/12 09:19:30.918 +00:00] [INFO] [client.go:336] ["coarse scatter"] [enabled=true] +181:[2026/06/12 09:19:34.039 +00:00] [INFO] [split.go:97] ["execute split sorted keys"] ["keys count"=8] +184:[2026/06/12 09:19:34.053 +00:00] [INFO] [client.go:240] ["scatter regions"] [regions=3] +189:[2026/06/12 09:19:34.066 +00:00] [INFO] [split.go:121] ["finish spliting regions roughly"] [take=26.895772ms] +192:[2026/06/12 09:19:34.082 +00:00] [INFO] [split.go:151] ["finish splitting regions."] [take=16.065814ms] +193:[2026/06/12 09:19:34.082 +00:00] [INFO] [split.go:128] ["finish spliting and scattering regions"] [take=43.022482ms] +``` + +Interpretation: + +- Both restores used `split-keys=8`. +- Default restore logged `coarse scatter enabled=false` and 5 `scatter regions` calls. One call happened before rough split completed, and four calls happened after rough split completed, which are fine split scatter calls. +- Coarse restore logged `coarse scatter enabled=true` and only 1 `scatter regions` call. That call happened before `finish spliting regions roughly`. +- After rough split completed in the coarse restore log, BR logged `finish splitting regions.` for the fine split pass and did not log any more `scatter regions` calls. + +This proves `--corase-scatter=true` preserved rough split scatter and skipped fine split scatter in a real TiUP playground restore. + +## Step 8. Cleanup + +Command: + +```bash +kill "$PLAYGROUND_PID" +rm -rf "$HOME/.tiup/data/br-coarse-scatter-verify" +curl -sf http://127.0.0.1:2379/pd/api/v1/version || echo cleaned +``` + +Output: + +```text +PD endpoint unreachable after cleanup +``` + +Artifacts left intentionally for inspection: + +```text +/tmp/br-coarse-scatter-verify/restore-default.log +/tmp/br-coarse-scatter-verify/restore-coarse.log +/tmp/br-coarse-scatter-verify/playground.log +``` From f4da686a115f6155f6d418629b77d4056b6e1360 Mon Sep 17 00:00:00 2001 From: Juncen Yu Date: Mon, 15 Jun 2026 06:19:29 +0000 Subject: [PATCH 3/5] fixed typo Signed-off-by: Juncen Yu --- br/pkg/task/restore.go | 4 +- br/pkg/task/restore_nokit_test.go | 2 +- br_coarse_scatter_playground_verification.md | 272 ------------------- 3 files changed, 3 insertions(+), 275 deletions(-) delete mode 100644 br_coarse_scatter_playground_verification.md diff --git a/br/pkg/task/restore.go b/br/pkg/task/restore.go index fe890149e9d8b..1b9094af885d5 100644 --- a/br/pkg/task/restore.go +++ b/br/pkg/task/restore.go @@ -91,7 +91,7 @@ const ( // FlagSplitRegionIndexStep controls the split-key index step used by rough split during snapshot restore. FlagSplitRegionIndexStep = "split-region-index-step" // FlagCoarseScatter controls whether only rough split regions are scattered during snapshot restore. - FlagCoarseScatter = "corase-scatter" + FlagCoarseScatter = "coarse-scatter" // FlagStatsConcurrency controls concurrency to restore statistic. FlagStatsConcurrency = "stats-concurrency" // FlagBatchFlushInterval controls after how long the restore batch would be auto sended. @@ -277,7 +277,7 @@ type RestoreConfig struct { PDConcurrency uint `json:"pd-concurrency" toml:"pd-concurrency"` RegionScanConcurrency uint `json:"region-scan-concurrency" toml:"region-scan-concurrency"` SplitRegionIndexStep uint `json:"split-region-index-step" toml:"split-region-index-step"` - CoarseScatter bool `json:"corase-scatter" toml:"corase-scatter"` + CoarseScatter bool `json:"coarse-scatter" toml:"coarse-scatter"` StatsConcurrency uint `json:"stats-concurrency" toml:"stats-concurrency"` BatchFlushInterval time.Duration `json:"batch-flush-interval" toml:"batch-flush-interval"` // DdlBatchSize use to define the size of batch ddl to create tables diff --git a/br/pkg/task/restore_nokit_test.go b/br/pkg/task/restore_nokit_test.go index 51bd09cf9dca5..5b3819faa5176 100644 --- a/br/pkg/task/restore_nokit_test.go +++ b/br/pkg/task/restore_nokit_test.go @@ -58,7 +58,7 @@ func TestSplitRegionIndexStepFlag(t *testing.T) { t.Run("coarse scatter", func(t *testing.T) { flags := pflag.NewFlagSet("restore", pflag.ContinueOnError) DefineRestoreFlags(flags) - require.NoError(t, flags.Set(FlagCoarseScatter, "true")) + require.NoError(t, flags.Set("coarse-scatter", "true")) cfg := &RestoreConfig{} require.NoError(t, cfg.ParseFromFlags(flags, true)) diff --git a/br_coarse_scatter_playground_verification.md b/br_coarse_scatter_playground_verification.md deleted file mode 100644 index f0c6f77a989af..0000000000000 --- a/br_coarse_scatter_playground_verification.md +++ /dev/null @@ -1,272 +0,0 @@ -# BR coarse scatter playground verification - -Date: 2026-06-12 UTC - -Purpose: verify with a real TiUP playground that `br restore ... --corase-scatter=true` only scatters rough split regions. - -## Step 0. Build the local BR binary - -Command: - -```bash -go build -o /tmp/br-coarse-scatter ./br/cmd/br -/tmp/br-coarse-scatter --version -/tmp/br-coarse-scatter restore db --help | grep -- --corase-scatter -``` - -Output: - -```text -Release Version: nightly-dirty -Git Commit Hash: None -Git Branch: None -Go Version: go1.25.10 -UTC Build Time: None -Race Enabled: false -Kernel Type: Classic - --corase-scatter only scatter regions from rough split during snapshot restore -``` - -Note: I first tried `tiup playground v8.5.6`, but the locally built BR uses a newer PD client and backup repeatedly failed with `rpc error: code = Unimplemented desc = unknown method QueryRegion for service pdpb.PD`. The successful verification below uses the already installed `v9.0.0-beta.2.pre-nightly` playground components. - -## Step 1. Start playground - -Command: - -```bash -tiup playground v9.0.0-beta.2.pre-nightly --db 1 --pd 1 --kv 3 --tiflash 0 --without-monitor --tag br-coarse-scatter-verify -curl -sf http://127.0.0.1:2379/pd/api/v1/version -mysql -h 127.0.0.1 -P 4000 -u root -e 'select version();' -curl -s http://127.0.0.1:2379/pd/api/v1/config/replicate -``` - -Output: - -```text -playground pid: 2367090 -{ - "version": "v9.0.0-beta.2.pre-335-g5e71b3a", - "build_time": "", - "hash": "", - "branch": "" -} -version() -8.0.11-TiDB-v9.0.0-beta.2.pre-1504-ga5ad421c49 -up stores: 3 -{ - "max-replicas": 3, - "location-labels": "", - "strictly-match-label": "false", - "enable-placement-rules": "true", - "enable-placement-rules-cache": "false", - "isolation-level": "" -} -``` - -Why this matters: BR only performs scatter when `store-count >= max-replicas` and `store-count > 1`. This playground has 3 Up stores and `max-replicas = 3`, so scatter is active. - -## Step 2. Create split source databases - -Command: - -```sql -DROP DATABASE IF EXISTS br_cs_default; -DROP DATABASE IF EXISTS br_cs_coarse; -CREATE DATABASE br_cs_default; -CREATE DATABASE br_cs_coarse; -CREATE TABLE br_cs_default.t (id BIGINT PRIMARY KEY, v VARCHAR(32)); -CREATE TABLE br_cs_coarse.t (id BIGINT PRIMARY KEY, v VARCHAR(32)); -SPLIT TABLE br_cs_default.t BETWEEN (0) AND (80000) REGIONS 8; -SPLIT TABLE br_cs_coarse.t BETWEEN (0) AND (80000) REGIONS 8; -INSERT INTO br_cs_default.t VALUES (5000,'v5000'),(15000,'v15000'),(25000,'v25000'),(35000,'v35000'),(45000,'v45000'),(55000,'v55000'),(65000,'v65000'),(75000,'v75000'); -INSERT INTO br_cs_coarse.t VALUES (5000,'v5000'),(15000,'v15000'),(25000,'v25000'),(35000,'v35000'),(45000,'v45000'),(55000,'v55000'),(65000,'v65000'),(75000,'v75000'); -SELECT 'br_cs_default' AS db_name, COUNT(*) AS row_count FROM br_cs_default.t; -SELECT 'br_cs_coarse' AS db_name, COUNT(*) AS row_count FROM br_cs_coarse.t; -``` - -Output: - -```text -SPLIT TABLE br_cs_default.t BETWEEN (0) AND (80000) REGIONS 8 -+--------------------+----------------------+ -| TOTAL_SPLIT_REGION | SCATTER_FINISH_RATIO | -+--------------------+----------------------+ -| 7 | 1 | -+--------------------+----------------------+ - -SPLIT TABLE br_cs_coarse.t BETWEEN (0) AND (80000) REGIONS 8 -+--------------------+----------------------+ -| TOTAL_SPLIT_REGION | SCATTER_FINISH_RATIO | -+--------------------+----------------------+ -| 7 | 1 | -+--------------------+----------------------+ - -INSERT INTO br_cs_default.t ... -Query OK, 8 rows affected - -INSERT INTO br_cs_coarse.t ... -Query OK, 8 rows affected - -+---------------+-----------+ -| db_name | row_count | -+---------------+-----------+ -| br_cs_default | 8 | -+---------------+-----------+ - -+--------------+-----------+ -| db_name | row_count | -+--------------+-----------+ -| br_cs_coarse | 8 | -+--------------+-----------+ -``` - -## Step 3. Back up both databases - -Command: - -```bash -/tmp/br-coarse-scatter backup db --db br_cs_default --pd 127.0.0.1:2379 -s local:///tmp/br-coarse-scatter-verify/backup-default --log-file /tmp/br-coarse-scatter-verify/backup-default.log --checksum=false --check-requirements=false -/tmp/br-coarse-scatter backup db --db br_cs_coarse --pd 127.0.0.1:2379 -s local:///tmp/br-coarse-scatter-verify/backup-coarse --log-file /tmp/br-coarse-scatter-verify/backup-coarse.log --checksum=false --check-requirements=false -find /tmp/br-coarse-scatter-verify/backup-default -maxdepth 1 -type f | wc -l -find /tmp/br-coarse-scatter-verify/backup-coarse -maxdepth 1 -type f | wc -l -``` - -Output: - -```text -Detail BR log in /tmp/br-coarse-scatter-verify/backup-default.log -[2026/06/12 09:19:21.804 +00:00] [INFO] [collector.go:77] ["Database Backup success summary"] [total-ranges=8] [ranges-succeed=8] [ranges-failed=0] [backup-checksum=3.717806ms] [write-CF-files=8] [backup-total-ranges=1] [backup-total-regions=8] [total-take=3.493531474s] [backup-data-size(after-compressed)=14.08kB] [Size=14079] [BackupTS=466945561939935258] [total-kv=8] [total-kv-size=271B] [average-speed=77.57B/s] -Detail BR log in /tmp/br-coarse-scatter-verify/backup-coarse.log -[2026/06/12 09:19:25.329 +00:00] [INFO] [collector.go:77] ["Database Backup success summary"] [total-ranges=8] [ranges-succeed=8] [ranges-failed=0] [backup-checksum=1.6606ms] [backup-total-regions=8] [write-CF-files=8] [backup-total-ranges=1] [total-take=3.479387406s] [BackupTS=466945562870546436] [total-kv=8] [total-kv-size=271B] [average-speed=77.89B/s] [backup-data-size(after-compressed)=14.08kB] [Size=14079] -backup-default files: 4 -backup-coarse files: 4 -``` - -## Step 4. Drop source databases before restore - -Command: - -```sql -DROP DATABASE br_cs_default; -DROP DATABASE br_cs_coarse; -SHOW DATABASES LIKE 'br_cs_%'; -``` - -Output: - -```text -DROP DATABASE br_cs_default -Query OK, 0 rows affected - -DROP DATABASE br_cs_coarse -Query OK, 0 rows affected - -SHOW DATABASES LIKE 'br_cs_%' -Empty set -``` - -## Step 5. Restore with default scatter behavior - -Command: - -```bash -/tmp/br-coarse-scatter restore db --db br_cs_default --pd 127.0.0.1:2379 -s local:///tmp/br-coarse-scatter-verify/backup-default --log-file /tmp/br-coarse-scatter-verify/restore-default.log --checksum=false --check-requirements=false --load-stats=false --split-region-index-step=2 --merge-region-size-bytes=1 --merge-region-key-count=1 -mysql -h 127.0.0.1 -P 4000 -u root -e "SELECT COUNT(*) AS default_rows FROM br_cs_default.t;" -``` - -Output: - -```text -Detail BR log in /tmp/br-coarse-scatter-verify/restore-default.log -[2026/06/12 09:19:28.754 +00:00] [INFO] [collector.go:77] ["DataBase Restore success summary"] [total-ranges=8] [ranges-succeed=8] [ranges-failed=0] [merge-ranges=491.94us] [split-regions=41.509104ms] [restore-files=16.351669ms] [restore-pipeline=63.655us] [default-CF-files=0] [write-CF-files=8] [split-keys=8] [total-take=3.266305201s] [restore-data-size(after-compressed)=14.08kB] [Size=14079] [BackupTS=466945561939935258] [RestoreTS=466945563840479237] [total-kv=8] [total-kv-size=271B] [average-speed=82.97B/s] -default_rows -8 -``` - -## Step 6. Restore with coarse scatter enabled - -Command: - -```bash -/tmp/br-coarse-scatter restore db --db br_cs_coarse --pd 127.0.0.1:2379 -s local:///tmp/br-coarse-scatter-verify/backup-coarse --log-file /tmp/br-coarse-scatter-verify/restore-coarse.log --checksum=false --check-requirements=false --load-stats=false --split-region-index-step=2 --merge-region-size-bytes=1 --merge-region-key-count=1 --corase-scatter=true -mysql -h 127.0.0.1 -P 4000 -u root -e "SELECT COUNT(*) AS coarse_rows FROM br_cs_coarse.t;" -``` - -Output: - -```text -Detail BR log in /tmp/br-coarse-scatter-verify/restore-coarse.log -[2026/06/12 09:19:34.109 +00:00] [INFO] [collector.go:77] ["DataBase Restore success summary"] [total-ranges=8] [ranges-succeed=8] [ranges-failed=0] [merge-ranges=151.758us] [split-regions=43.057546ms] [restore-files=6.990495ms] [restore-pipeline=78.206us] [split-keys=8] [default-CF-files=0] [write-CF-files=8] [total-take=3.254340464s] [Size=14079] [BackupTS=466945562870546436] [RestoreTS=466945565242949700] [total-kv=8] [total-kv-size=271B] [average-speed=83.27B/s] [restore-data-size(after-compressed)=14.08kB] -coarse_rows -8 -``` - -## Step 7. Proof from BR restore logs - -Command: - -```bash -grep -c '"scatter regions"' /tmp/br-coarse-scatter-verify/restore-default.log -grep -c '"scatter regions"' /tmp/br-coarse-scatter-verify/restore-coarse.log -grep -nE 'coarse scatter|execute split sorted keys|"scatter regions"|finish spliting regions roughly|finish spliting and scattering regions|finish splitting regions\.' /tmp/br-coarse-scatter-verify/restore-default.log -grep -nE 'coarse scatter|execute split sorted keys|"scatter regions"|finish spliting regions roughly|finish spliting and scattering regions|finish splitting regions\.' /tmp/br-coarse-scatter-verify/restore-coarse.log -``` - -Output: - -```text -default restore scatter log count: 5 -coarse restore scatter log count: 1 - --- default log key lines -- -92:[2026/06/12 09:19:25.550 +00:00] [INFO] [client.go:336] ["coarse scatter"] [enabled=false] -181:[2026/06/12 09:19:28.675 +00:00] [INFO] [split.go:97] ["execute split sorted keys"] ["keys count"=8] -184:[2026/06/12 09:19:28.690 +00:00] [INFO] [client.go:240] ["scatter regions"] [regions=3] -189:[2026/06/12 09:19:28.702 +00:00] [INFO] [split.go:121] ["finish spliting regions roughly"] [take=26.271794ms] -192:[2026/06/12 09:19:28.714 +00:00] [INFO] [client.go:240] ["scatter regions"] [regions=1] -193:[2026/06/12 09:19:28.714 +00:00] [INFO] [client.go:240] ["scatter regions"] [regions=1] -194:[2026/06/12 09:19:28.715 +00:00] [INFO] [client.go:240] ["scatter regions"] [regions=1] -195:[2026/06/12 09:19:28.715 +00:00] [INFO] [client.go:240] ["scatter regions"] [regions=2] -199:[2026/06/12 09:19:28.717 +00:00] [INFO] [split.go:128] ["finish spliting and scattering regions"] [take=41.467275ms] - --- coarse log key lines -- -92:[2026/06/12 09:19:30.918 +00:00] [INFO] [client.go:336] ["coarse scatter"] [enabled=true] -181:[2026/06/12 09:19:34.039 +00:00] [INFO] [split.go:97] ["execute split sorted keys"] ["keys count"=8] -184:[2026/06/12 09:19:34.053 +00:00] [INFO] [client.go:240] ["scatter regions"] [regions=3] -189:[2026/06/12 09:19:34.066 +00:00] [INFO] [split.go:121] ["finish spliting regions roughly"] [take=26.895772ms] -192:[2026/06/12 09:19:34.082 +00:00] [INFO] [split.go:151] ["finish splitting regions."] [take=16.065814ms] -193:[2026/06/12 09:19:34.082 +00:00] [INFO] [split.go:128] ["finish spliting and scattering regions"] [take=43.022482ms] -``` - -Interpretation: - -- Both restores used `split-keys=8`. -- Default restore logged `coarse scatter enabled=false` and 5 `scatter regions` calls. One call happened before rough split completed, and four calls happened after rough split completed, which are fine split scatter calls. -- Coarse restore logged `coarse scatter enabled=true` and only 1 `scatter regions` call. That call happened before `finish spliting regions roughly`. -- After rough split completed in the coarse restore log, BR logged `finish splitting regions.` for the fine split pass and did not log any more `scatter regions` calls. - -This proves `--corase-scatter=true` preserved rough split scatter and skipped fine split scatter in a real TiUP playground restore. - -## Step 8. Cleanup - -Command: - -```bash -kill "$PLAYGROUND_PID" -rm -rf "$HOME/.tiup/data/br-coarse-scatter-verify" -curl -sf http://127.0.0.1:2379/pd/api/v1/version || echo cleaned -``` - -Output: - -```text -PD endpoint unreachable after cleanup -``` - -Artifacts left intentionally for inspection: - -```text -/tmp/br-coarse-scatter-verify/restore-default.log -/tmp/br-coarse-scatter-verify/restore-coarse.log -/tmp/br-coarse-scatter-verify/playground.log -``` From 58381e8f8e8b60905ab2ba808014f313ab985885 Mon Sep 17 00:00:00 2001 From: Juncen Yu Date: Mon, 15 Jun 2026 08:29:01 +0000 Subject: [PATCH 4/5] br: make SplitKeys no-scatter method --- br/pkg/restore/split/client.go | 15 ++++++---- br/pkg/restore/split/client_test.go | 2 +- br/pkg/restore/split/mock_pd_client.go | 16 ++++++++++ br/pkg/restore/split/split.go | 6 +--- br/pkg/restore/split/split_test.go | 41 +++++--------------------- 5 files changed, 35 insertions(+), 45 deletions(-) diff --git a/br/pkg/restore/split/client.go b/br/pkg/restore/split/client.go index c25225a32762b..89a6cf796c5e1 100644 --- a/br/pkg/restore/split/client.go +++ b/br/pkg/restore/split/client.go @@ -69,6 +69,9 @@ type SplitClient interface { // new regions. It returns the new regions that need to be called with // WaitRegionsScattered. SplitKeysAndScatter(ctx context.Context, sortedSplitKeys [][]byte) ([]*RegionInfo, error) + // SplitKeys splits the related regions of the keys without scattering the new + // regions. + SplitKeys(ctx context.Context, sortedSplitKeys [][]byte) ([]*RegionInfo, error) // SplitWaitAndScatter splits a region from a batch of keys, waits for the split // is finished, and scatters the new regions. It will return the original region, @@ -123,10 +126,6 @@ type SplitClient interface { GetCodecPDClient() *tikvclient.CodecPDClient } -type splitClientWithScatterControl interface { - SplitKeys(ctx context.Context, sortedSplitKeys [][]byte, scatter bool) ([]*RegionInfo, error) -} - // pdClient is a wrapper of pd client, can be used by RegionSplitter. type pdClient struct { mu sync.Mutex @@ -631,10 +630,14 @@ func (c *pdClient) getEncodedKeys(start, end []byte) (encodedStart, encodedEnd [ } func (c *pdClient) SplitKeysAndScatter(ctx context.Context, sortedSplitKeys [][]byte) ([]*RegionInfo, error) { - return c.SplitKeys(ctx, sortedSplitKeys, true) + return c.splitKeys(ctx, sortedSplitKeys, true) +} + +func (c *pdClient) SplitKeys(ctx context.Context, sortedSplitKeys [][]byte) ([]*RegionInfo, error) { + return c.splitKeys(ctx, sortedSplitKeys, false) } -func (c *pdClient) SplitKeys(ctx context.Context, sortedSplitKeys [][]byte, scatter bool) ([]*RegionInfo, error) { +func (c *pdClient) splitKeys(ctx context.Context, sortedSplitKeys [][]byte, scatter bool) ([]*RegionInfo, error) { if len(sortedSplitKeys) == 0 { return nil, nil } diff --git a/br/pkg/restore/split/client_test.go b/br/pkg/restore/split/client_test.go index 17323ccef39bb..c451a143a0d58 100644 --- a/br/pkg/restore/split/client_test.go +++ b/br/pkg/restore/split/client_test.go @@ -130,7 +130,7 @@ func TestSplitScatter(t *testing.T) { for _, key := range splitKeys { noScatterSplitKeys = append(noScatterSplitKeys, append([]byte(nil), key...)) } - _, err := noScatterClient.SplitKeys(ctx, noScatterSplitKeys, false) + _, err := noScatterClient.SplitKeys(ctx, noScatterSplitKeys) require.NoError(t, err) require.Positive(t, noScatterPDClient.splitRegions.count) require.Zero(t, noScatterPDClient.scatterRegions.regionCount) diff --git a/br/pkg/restore/split/mock_pd_client.go b/br/pkg/restore/split/mock_pd_client.go index 999c5aec1159f..6b904d4c31f85 100644 --- a/br/pkg/restore/split/mock_pd_client.go +++ b/br/pkg/restore/split/mock_pd_client.go @@ -110,6 +110,14 @@ func (c *TestClient) GetRegionByID(ctx context.Context, regionID uint64) (*Regio return region, nil } +func (c *TestClient) SplitKeysAndScatter(ctx context.Context, keys [][]byte) ([]*RegionInfo, error) { + return c.SplitWaitAndScatter(ctx, nil, keys) +} + +func (c *TestClient) SplitKeys(ctx context.Context, keys [][]byte) ([]*RegionInfo, error) { + return c.SplitWaitAndScatter(ctx, nil, keys) +} + func (c *TestClient) SplitWaitAndScatter(_ context.Context, _ *RegionInfo, keys [][]byte) ([]*RegionInfo, error) { c.mu.Lock() defer c.mu.Unlock() @@ -690,6 +698,14 @@ func (f *FakeSplitClient) WaitRegionsScattered(context.Context, []*RegionInfo) ( return 0, nil } +func (f *FakeSplitClient) SplitKeysAndScatter(context.Context, [][]byte) ([]*RegionInfo, error) { + return nil, nil +} + +func (f *FakeSplitClient) SplitKeys(context.Context, [][]byte) ([]*RegionInfo, error) { + return nil, nil +} + func (*FakeSplitClient) GetCodecPDClient() *tikvclient.CodecPDClient { return nil } diff --git a/br/pkg/restore/split/split.go b/br/pkg/restore/split/split.go index 0be18d9149915..1d96e8b70355d 100644 --- a/br/pkg/restore/split/split.go +++ b/br/pkg/restore/split/split.go @@ -157,11 +157,7 @@ func (rs *RegionSplitter) splitKeys(ctx context.Context, sortedKeys [][]byte, sc if scatter { return rs.client.SplitKeysAndScatter(ctx, sortedKeys) } - client, ok := rs.client.(splitClientWithScatterControl) - if !ok { - return nil, errors.New("split client does not support split without scatter") - } - return client.SplitKeys(ctx, sortedKeys, false) + return rs.client.SplitKeys(ctx, sortedKeys) } // waitRegionsScattered try to wait mutilple regions scatterd in 3 minutes. diff --git a/br/pkg/restore/split/split_test.go b/br/pkg/restore/split/split_test.go index 8b8ae7f9442a9..f65e740d596e8 100644 --- a/br/pkg/restore/split/split_test.go +++ b/br/pkg/restore/split/split_test.go @@ -551,36 +551,6 @@ func TestPaginateScanRegionWithCodecAwareCodecPDClient(t *testing.T) { }) } -func TestSplitKeysAndScatterCodecPDClientMapsSplitKeys(t *testing.T) { - require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/br/pkg/restore/split/failToSplit", "return()")) - t.Cleanup(func() { - require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/br/pkg/restore/split/failToSplit")) - }) - originSplitRetryTimes := SplitRetryTimes - SplitRetryTimes = 1 - t.Cleanup(func() { - SplitRetryTimes = originSplitRetryTimes - }) - - ctx := context.Background() - mockPDClient := NewMockPDClientForSplit() - codecPDClient := newCodecV2PDClientForSplitTest(t, mockPDClient) - tikvCodec := codecPDClient.GetCodec() - mockPDClient.SetRegions([][]byte{ - tikvCodec.EncodeRegionKey([]byte("a")), - tikvCodec.EncodeRegionKey([]byte("d")), - }) - - client := &pdClient{ - client: codecPDClient, - splitConcurrency: 1, - splitBatchKeyCnt: 100, - isCodecPDClient: true, - } - _, err := client.SplitKeysAndScatter(ctx, [][]byte{tikvCodec.EncodeKey([]byte("b"))}) - require.ErrorContains(t, err, "retryable error") -} - func TestPaginateScanRegion(t *testing.T) { ctx := context.Background() mockPDClient := NewMockPDClientForSplit() @@ -1084,13 +1054,18 @@ type recordingSplitClient struct { } func (c *recordingSplitClient) SplitKeysAndScatter(ctx context.Context, keys [][]byte) ([]*RegionInfo, error) { - return c.SplitKeys(ctx, keys, true) + c.recordSplit(keys, true) + return nil, nil } -func (c *recordingSplitClient) SplitKeys(_ context.Context, keys [][]byte, scatter bool) ([]*RegionInfo, error) { +func (c *recordingSplitClient) SplitKeys(_ context.Context, keys [][]byte) ([]*RegionInfo, error) { + c.recordSplit(keys, false) + return nil, nil +} + +func (c *recordingSplitClient) recordSplit(keys [][]byte, scatter bool) { c.splitCalls = append(c.splitCalls, slices.Clone(keys)) c.scatterByCalls = append(c.scatterByCalls, scatter) - return nil, nil } func TestRegionSplitterRoughSplitUsesConfiguredRegionIndexStep(t *testing.T) { From 93c8e5dd53fb597deafaecb77c566071cd3617dc Mon Sep 17 00:00:00 2001 From: Juncen Yu Date: Tue, 16 Jun 2026 02:18:43 +0000 Subject: [PATCH 5/5] build: update split test shard count --- br/pkg/restore/split/BUILD.bazel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/br/pkg/restore/split/BUILD.bazel b/br/pkg/restore/split/BUILD.bazel index a4426aad1c6e3..5c8526a2fe790 100644 --- a/br/pkg/restore/split/BUILD.bazel +++ b/br/pkg/restore/split/BUILD.bazel @@ -67,7 +67,7 @@ go_test( ], embed = [":split"], flaky = True, - shard_count = 35, + shard_count = 34, deps = [ "//br/pkg/errors", "//br/pkg/restore/utils",