diff --git a/pkg/keyspace/util.go b/pkg/keyspace/util.go index c8b306efb51..1bb1f6c2900 100644 --- a/pkg/keyspace/util.go +++ b/pkg/keyspace/util.go @@ -111,6 +111,21 @@ func MakeKeyspacePrefix(mode byte, id uint32) []byte { return prefix } +// ParseKeyspacePrefix parses a raw keyspace prefix from key. +// It returns false for keys that do not start with a known keyspace mode byte. +func ParseKeyspacePrefix(key []byte) (mode byte, id uint32, ok bool) { + if len(key) < KeyspacePrefixLen { + return 0, 0, false + } + mode = key[0] + if mode != RawKeyspaceModePrefix && mode != TxnKeyspaceModePrefix { + return 0, 0, false + } + idBytes := [KeyspacePrefixLen]byte{0, key[1], key[2], key[3]} + id = binary.BigEndian.Uint32(idBytes[:]) + return mode, id, true +} + // RegionBound represents the region boundary of the given keyspace. // For a keyspace with id ['a', 'b', 'c'], it has four boundaries: // diff --git a/pkg/keyspace/util_test.go b/pkg/keyspace/util_test.go index 77d19dde732..b27bdcbaf41 100644 --- a/pkg/keyspace/util_test.go +++ b/pkg/keyspace/util_test.go @@ -140,6 +140,44 @@ func TestMaxKeyspaceLabelRuleSplitKeys(t *testing.T) { ) } +func TestParseKeyspacePrefix(t *testing.T) { + re := require.New(t) + + testCases := []struct { + name string + key []byte + mode byte + id uint32 + }{ + { + name: "raw", + key: []byte{'r', 0x01, 0x02, 0x03}, + mode: RawKeyspaceModePrefix, + id: 0x010203, + }, + { + name: "txn with suffix", + key: []byte{'x', 0xff, 0xff, 0xff, 't'}, + mode: TxnKeyspaceModePrefix, + id: constant.MaxValidKeyspaceID, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(_ *testing.T) { + mode, id, ok := ParseKeyspacePrefix(testCase.key) + re.True(ok) + re.Equal(testCase.mode, mode) + re.Equal(testCase.id, id) + }) + } + + _, _, ok := ParseKeyspacePrefix([]byte{'x', 0x01, 0x02}) + re.False(ok) + _, _, ok = ParseKeyspacePrefix([]byte{'t', 0x01, 0x02, 0x03}) + re.False(ok) +} + func TestValidateName(t *testing.T) { re := require.New(t) testCases := []struct { diff --git a/pkg/schedule/checker/split_scatter.go b/pkg/schedule/checker/split_scatter.go index d3044b67ddb..70696d5c5e2 100644 --- a/pkg/schedule/checker/split_scatter.go +++ b/pkg/schedule/checker/split_scatter.go @@ -15,6 +15,7 @@ package checker import ( + "bytes" "context" "fmt" "sort" @@ -24,6 +25,7 @@ import ( "github.com/pingcap/log" + "github.com/tikv/pd/pkg/keyspace" sche "github.com/tikv/pd/pkg/schedule/core" "github.com/tikv/pd/pkg/schedule/filter" "github.com/tikv/pd/pkg/schedule/operator" @@ -89,6 +91,16 @@ type splitScatterRangeHint struct { scatterGroup string } +func (c *splitScatterController) hasSplitScatterTxnKeyspaceBounds(keyspaceID uint32) bool { + regionBound := keyspace.MakeRegionBound(keyspaceID) + return c.hasRegionStartKey(regionBound.TxnLeftBound) && c.hasRegionStartKey(regionBound.TxnRightBound) +} + +func (c *splitScatterController) hasRegionStartKey(key []byte) bool { + region := c.cluster.GetRegionByKey(key) + return region != nil && bytes.Equal(region.GetStartKey(), key) +} + func (c *splitScatterController) collectTopPendingSplitScatter(limit int) []splitScatterPendingItem { if limit <= 0 { return nil @@ -343,7 +355,7 @@ func (c *splitScatterController) dispatchSplitScatterRegions() { splitScatterDispatchRegionMissingCounter.Inc() continue } - rangeHint := resolveSplitScatterRangeHint(region) + rangeHint := resolveSplitScatterRangeHintWithKeyspaceValidator(region, c.hasSplitScatterTxnKeyspaceBounds) scatterGroup := pending.group if rangeHint.scatterGroup != "" { scatterGroup = rangeHint.scatterGroup diff --git a/pkg/schedule/checker/split_scatter_group.go b/pkg/schedule/checker/split_scatter_group.go index 817ae311106..5c52d5a2b4c 100644 --- a/pkg/schedule/checker/split_scatter_group.go +++ b/pkg/schedule/checker/split_scatter_group.go @@ -20,6 +20,7 @@ import ( "github.com/tikv/pd/pkg/codec" "github.com/tikv/pd/pkg/core" + "github.com/tikv/pd/pkg/keyspace" ) var ( @@ -27,12 +28,32 @@ var ( splitScatterIndexPrefix = []byte("_i") ) -func resolveSplitScatterRangeHint(region *core.RegionInfo) splitScatterRangeHint { - _, decoded, err := codec.DecodeBytes(region.GetStartKey()) - if err != nil || !bytes.HasPrefix(decoded, splitScatterTablePrefix) { +type splitScatterKeyspaceValidator func(uint32) bool + +type splitScatterDecodedKey struct { + rawKey []byte + keyspacePrefix []byte + keyspaceID uint32 +} + +func (key splitScatterDecodedKey) hasKeyspace() bool { + return len(key.keyspacePrefix) > 0 +} + +func resolveSplitScatterRangeHintWithKeyspaceValidator( + region *core.RegionInfo, + validateKeyspace splitScatterKeyspaceValidator, +) splitScatterRangeHint { + decodedKey, err := decodeSplitScatterRegionKey(region.GetStartKey(), validateKeyspace) + if err != nil { return splitScatterRangeHint{} } - rest := decoded[len(splitScatterTablePrefix):] + rawKey := decodedKey.rawKey + + if !bytes.HasPrefix(rawKey, splitScatterTablePrefix) { + return splitScatterRangeHint{} + } + rest := rawKey[len(splitScatterTablePrefix):] rest, tableID, err := codec.DecodeInt(rest) if err != nil { return splitScatterRangeHint{} @@ -40,8 +61,11 @@ func resolveSplitScatterRangeHint(region *core.RegionInfo) splitScatterRangeHint tablePrefix := append([]byte(nil), codec.GenerateTableKey(tableID)...) tableGroup := makeSplitScatterTableGroup(tableID) + if decodedKey.hasKeyspace() { + tableGroup = makeSplitScatterKeyspaceTableGroup(decodedKey.keyspaceID, tableID) + } if !bytes.HasPrefix(rest, splitScatterIndexPrefix) { - return splitScatterPrefixRangeWithGroup(tablePrefix, tableGroup) + return splitScatterPrefixRangeWithKeyspaceGroup(decodedKey.keyspacePrefix, tablePrefix, tableGroup) } indexRest := rest[len(splitScatterIndexPrefix):] @@ -52,7 +76,10 @@ func resolveSplitScatterRangeHint(region *core.RegionInfo) splitScatterRangeHint indexPrefix := codec.GenerateIndexKey(tableID, indexID) indexGroup := makeSplitScatterIndexGroup(tableID, indexID) - indexRange := splitScatterPrefixRangeWithGroup(indexPrefix, indexGroup) + if decodedKey.hasKeyspace() { + indexGroup = makeSplitScatterKeyspaceIndexGroup(decodedKey.keyspaceID, tableID, indexID) + } + indexRange := splitScatterPrefixRangeWithKeyspaceGroup(decodedKey.keyspacePrefix, indexPrefix, indexGroup) endKey := region.GetEndKey() // We intentionally over-approximate ambiguous table-key ranges. If PD can @@ -63,17 +90,48 @@ func resolveSplitScatterRangeHint(region *core.RegionInfo) splitScatterRangeHint // Both endKey and indexRange.endKey are MemComparable-encoded, so // bytes.Compare correctly reflects the key ordering. if len(endKey) == 0 || len(indexRange.startKey) == 0 || len(indexRange.endKey) == 0 || bytes.Compare(endKey, indexRange.endKey) > 0 { - return splitScatterPrefixRangeWithGroup(tablePrefix, tableGroup) + return splitScatterPrefixRangeWithKeyspaceGroup(decodedKey.keyspacePrefix, tablePrefix, tableGroup) } return indexRange } +func decodeSplitScatterRegionKey( + regionKey []byte, + validateKeyspace splitScatterKeyspaceValidator, +) (splitScatterDecodedKey, error) { + _, rawKey, err := codec.DecodeBytes(regionKey) + if err != nil { + return splitScatterDecodedKey{}, err + } + decodedKey := splitScatterDecodedKey{rawKey: rawKey} + mode, keyspaceID, ok := keyspace.ParseKeyspacePrefix(rawKey) + if !ok || mode != keyspace.TxnKeyspaceModePrefix { + return decodedKey, nil + } + + // Split-scatter range hints are table/index-scoped, so only TiDB txn + // keyspace keys from a known keyspace range are decoded. With only a + // region key, an API V2 txn prefix can be indistinguishable from a + // classic/raw user key that starts with the same bytes. + if validateKeyspace == nil || !validateKeyspace(keyspaceID) { + return decodedKey, nil + } + decodedKey.rawKey = rawKey[keyspace.KeyspacePrefixLen:] + decodedKey.keyspacePrefix = keyspace.MakeKeyspacePrefix(mode, keyspaceID) + decodedKey.keyspaceID = keyspaceID + return decodedKey, nil +} + func splitScatterPrefixRange(rawPrefix []byte) splitScatterRangeHint { return splitScatterPrefixRangeWithGroup(rawPrefix, "") } func splitScatterPrefixRangeWithGroup(rawPrefix []byte, scatterGroup string) splitScatterRangeHint { - startKey := append([]byte(nil), codec.EncodeBytes(rawPrefix)...) + return splitScatterPrefixRangeWithKeyspaceGroup(nil, rawPrefix, scatterGroup) +} + +func splitScatterPrefixRangeWithKeyspaceGroup(keyspacePrefix, rawPrefix []byte, scatterGroup string) splitScatterRangeHint { + startKey := codec.EncodeBytes(appendKeyspacePrefix(keyspacePrefix, rawPrefix)) endRawPrefix := splitScatterNextPrefix(rawPrefix) if len(endRawPrefix) == 0 { // Current callers use TiDB table/index prefixes, which always start @@ -84,11 +142,18 @@ func splitScatterPrefixRangeWithGroup(rawPrefix []byte, scatterGroup string) spl } return splitScatterRangeHint{ startKey: startKey, - endKey: append([]byte(nil), codec.EncodeBytes(endRawPrefix)...), + endKey: codec.EncodeBytes(appendKeyspacePrefix(keyspacePrefix, endRawPrefix)), scatterGroup: scatterGroup, } } +func appendKeyspacePrefix(keyspacePrefix, rawKey []byte) []byte { + key := make([]byte, 0, len(keyspacePrefix)+len(rawKey)) + key = append(key, keyspacePrefix...) + key = append(key, rawKey...) + return key +} + func makeSplitScatterTableGroup(tableID int64) string { return fmt.Sprintf("split-scatter-table-%d", tableID) } @@ -97,6 +162,14 @@ func makeSplitScatterIndexGroup(tableID, indexID int64) string { return fmt.Sprintf("split-scatter-index-%d-%d", tableID, indexID) } +func makeSplitScatterKeyspaceTableGroup(keyspaceID uint32, tableID int64) string { + return fmt.Sprintf("split-scatter-keyspace-%d-table-%d", keyspaceID, tableID) +} + +func makeSplitScatterKeyspaceIndexGroup(keyspaceID uint32, tableID, indexID int64) string { + return fmt.Sprintf("split-scatter-keyspace-%d-index-%d-%d", keyspaceID, tableID, indexID) +} + func splitScatterNextPrefix(key []byte) []byte { next := append([]byte(nil), key...) for i := len(next) - 1; i >= 0; i-- { diff --git a/pkg/schedule/checker/split_scatter_test.go b/pkg/schedule/checker/split_scatter_test.go index c2b74471620..78b725572b0 100644 --- a/pkg/schedule/checker/split_scatter_test.go +++ b/pkg/schedule/checker/split_scatter_test.go @@ -27,6 +27,8 @@ import ( "github.com/tikv/pd/pkg/codec" "github.com/tikv/pd/pkg/core" + "github.com/tikv/pd/pkg/keyspace" + "github.com/tikv/pd/pkg/keyspace/constant" "github.com/tikv/pd/pkg/mock/mockcluster" "github.com/tikv/pd/pkg/mock/mockconfig" "github.com/tikv/pd/pkg/schedule/hbstream" @@ -39,6 +41,8 @@ const ( splitScatterObservedRegionID uint64 = 101 splitScatterTestTableID int64 = 42 splitScatterTestIndexID int64 = 7 + splitScatterTestKeyspaceID uint32 = 4242 + splitScatterTestNextGenKeyspaceID uint32 = constant.SystemKeyspaceID splitScatterTestSourceWaitVersion = uint64(0) // CPU usage is only populated to mimic load-split region heartbeat data. // Current split-scatter dispatch does not rank pending regions by CPU. @@ -384,6 +388,7 @@ func TestCollectTopPendingResolvesRangeHint(t *testing.T) { endKey []byte wantRange splitScatterRangeHint wantGroup string + keyspaces []uint32 }{ { name: "index region", @@ -394,31 +399,39 @@ func TestCollectTopPendingResolvesRangeHint(t *testing.T) { }, { name: "record region", - startKey: newSplitScatterRecordKey(42, "a"), - endKey: newSplitScatterRecordKey(42, "m"), - wantRange: splitScatterPrefixRange(codec.GenerateTableKey(42)), - wantGroup: makeSplitScatterTableGroup(42), + startKey: newSplitScatterRecordKey(splitScatterTestTableID, "a"), + endKey: newSplitScatterRecordKey(splitScatterTestTableID, "m"), + wantRange: splitScatterPrefixRange(codec.GenerateTableKey(splitScatterTestTableID)), + wantGroup: makeSplitScatterTableGroup(splitScatterTestTableID), }, { name: "bare table boundary", - startKey: newSplitScatterTableBoundaryKey(42), + startKey: newSplitScatterTableBoundaryKey(splitScatterTestTableID), endKey: newSplitScatterIndexKey("m"), - wantRange: splitScatterPrefixRange(codec.GenerateTableKey(42)), - wantGroup: makeSplitScatterTableGroup(42), + wantRange: splitScatterPrefixRange(codec.GenerateTableKey(splitScatterTestTableID)), + wantGroup: makeSplitScatterTableGroup(splitScatterTestTableID), }, { name: "cross entity falls back to table", startKey: newSplitScatterIndexKey("a"), - endKey: newSplitScatterRecordKey(42, "m"), - wantRange: splitScatterPrefixRange(codec.GenerateTableKey(42)), - wantGroup: makeSplitScatterTableGroup(42), + endKey: newSplitScatterRecordKey(splitScatterTestTableID, "m"), + wantRange: splitScatterPrefixRange(codec.GenerateTableKey(splitScatterTestTableID)), + wantGroup: makeSplitScatterTableGroup(splitScatterTestTableID), }, { name: "cross table uses start table", - startKey: newSplitScatterRecordKey(42, "a"), - endKey: newSplitScatterRecordKey(43, "m"), - wantRange: splitScatterPrefixRange(codec.GenerateTableKey(42)), - wantGroup: makeSplitScatterTableGroup(42), + startKey: newSplitScatterRecordKey(splitScatterTestTableID, "a"), + endKey: newSplitScatterRecordKey(splitScatterTestTableID+1, "m"), + wantRange: splitScatterPrefixRange(codec.GenerateTableKey(splitScatterTestTableID)), + wantGroup: makeSplitScatterTableGroup(splitScatterTestTableID), + }, + { + name: "nextgen keyspace index region", + startKey: newSplitScatterKeyspaceIndexKey(splitScatterTestNextGenKeyspaceID, "a"), + endKey: newSplitScatterKeyspaceIndexKey(splitScatterTestNextGenKeyspaceID, "m"), + wantRange: splitScatterKeyspacePrefixRange(splitScatterTestNextGenKeyspaceID, splitScatterIndexKeyPrefix()), + wantGroup: makeSplitScatterKeyspaceIndexGroup(splitScatterTestNextGenKeyspaceID, splitScatterTestTableID, splitScatterTestIndexID), + keyspaces: []uint32{splitScatterTestNextGenKeyspaceID}, }, } @@ -430,12 +443,15 @@ func TestCollectTopPendingResolvesRangeHint(t *testing.T) { controller.RecordSplitScatterBatch(100, splitScatterTestSourceWaitVersion, []uint64{101}) putSplitScatterRegionWithKeys(tc, testCase.startKey, testCase.endKey, splitScatterReportedCPUUsage) - putSplitScatterRegion(tc, 100, "x", "z", splitScatterNoCPUUsage) + putSplitScatterRegion(tc, 100, "z", "", splitScatterNoCPUUsage) advanceSplitScatterSourceVersion(t, tc) re.Equal(makeSplitScatterGroup(100, 101), splitScatterPendingGroup(t, controller, 101)) re.ElementsMatch([]uint64{100, 101}, pendingRegionIDs(controller.collectTopPendingSplitScatter(2))) - rangeHint := resolveSplitScatterRangeHint(tc.GetRegion(101)) + rangeHint := resolveSplitScatterRangeHintWithKeyspaceValidator( + tc.GetRegion(101), + splitScatterKeyspaceValidatorFor(testCase.keyspaces...), + ) re.Equal(testCase.wantRange.startKey, rangeHint.startKey) re.Equal(testCase.wantRange.endKey, rangeHint.endKey) re.Equal(testCase.wantGroup, rangeHint.scatterGroup) @@ -443,6 +459,60 @@ func TestCollectTopPendingResolvesRangeHint(t *testing.T) { } } +func TestResolveSplitScatterRangeHintIgnoresRawLikeKeyspaceKeys(t *testing.T) { + re := require.New(t) + region := core.NewRegionInfo(&metapb.Region{ + Id: 1, + StartKey: newSplitScatterRawKeyspaceRecordKey(splitScatterTestKeyspaceID, splitScatterTestTableID, "a"), + EndKey: newSplitScatterRawKeyspaceRecordKey(splitScatterTestKeyspaceID, splitScatterTestTableID, "m"), + }, nil) + + rangeHint := resolveSplitScatterRangeHintWithKeyspaceValidator( + region, + splitScatterKeyspaceValidatorFor(splitScatterTestKeyspaceID), + ) + re.Equal(splitScatterRangeHint{}, rangeHint) +} + +func TestResolveSplitScatterRangeHintRequiresKnownTxnKeyspaceBounds(t *testing.T) { + testCases := []struct { + name string + keyspaceID uint32 + }{ + {name: "normal keyspace", keyspaceID: splitScatterTestKeyspaceID}, + {name: "max valid keyspace", keyspaceID: constant.MaxValidKeyspaceID}, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + re := require.New(t) + controller, tc, _, cleanup := newTestSplitScatterController(t) + defer cleanup() + + startKey := newSplitScatterKeyspaceRecordKey(testCase.keyspaceID, "a") + endKey := newSplitScatterKeyspaceRecordKey(testCase.keyspaceID, "m") + region := core.NewRegionInfo(&metapb.Region{ + Id: 1, + StartKey: startKey, + EndKey: endKey, + }, nil) + re.Equal(splitScatterRangeHint{}, resolveSplitScatterRangeHintWithKeyspaceValidator(region, nil)) + + regionBound := keyspace.MakeRegionBound(testCase.keyspaceID) + putSplitScatterRegionWithKeysByID(tc, 90, regionBound.TxnLeftBound, startKey, splitScatterNoCPUUsage) + putSplitScatterRegionWithKeysByID(tc, 91, regionBound.TxnRightBound, nil, splitScatterNoCPUUsage) + + rangeHint := resolveSplitScatterRangeHintWithKeyspaceValidator( + region, + controller.splitScatter.hasSplitScatterTxnKeyspaceBounds, + ) + wantRange := splitScatterKeyspacePrefixRange(testCase.keyspaceID, codec.GenerateTableKey(splitScatterTestTableID)) + wantRange.scatterGroup = makeSplitScatterKeyspaceTableGroup(testCase.keyspaceID, splitScatterTestTableID) + re.Equal(wantRange, rangeHint) + }) + } +} + func TestDispatchSplitScatterUsesRangeScatterGroup(t *testing.T) { re := require.New(t) controller, tc, oc, cleanup := newTestSplitScatterController(t) @@ -468,6 +538,39 @@ func TestDispatchSplitScatterUsesRangeScatterGroup(t *testing.T) { re.Equal(batchGroup, opBatchGroup) } +func TestDispatchSplitScatterUsesKeyspaceRangeScatterGroup(t *testing.T) { + re := require.New(t) + controller, tc, oc, cleanup := newTestSplitScatterController(t) + defer cleanup() + + controller.RecordSplitScatterBatch(100, splitScatterTestSourceWaitVersion, []uint64{101}) + startKey := newSplitScatterKeyspaceIndexKey(splitScatterTestKeyspaceID, "a") + endKey := newSplitScatterKeyspaceIndexKey(splitScatterTestKeyspaceID, "m") + regionBound := keyspace.MakeRegionBound(splitScatterTestKeyspaceID) + putSplitScatterRegionWithKeysByID(tc, 90, regionBound.TxnLeftBound, startKey, splitScatterNoCPUUsage) + putSplitScatterRegionWithKeysByID(tc, 91, regionBound.TxnRightBound, nil, splitScatterNoCPUUsage) + putSplitScatterRegionWithKeysByID(tc, 101, startKey, endKey, splitScatterReportedCPUUsage) + advanceSplitScatterRegionVersion(t, tc, 100) + + batchGroup := splitScatterPendingGroup(t, controller, 101) + + controller.dispatchSplitScatterRegions() + + expectedScatterGroup := makeSplitScatterKeyspaceIndexGroup( + splitScatterTestKeyspaceID, + splitScatterTestTableID, + splitScatterTestIndexID, + ) + op := oc.GetOperator(101) + re.NotNil(op) + opGroup, ok := op.GetAdditionalInfo("group") + re.True(ok) + re.Equal(expectedScatterGroup, opGroup) + opBatchGroup, ok := op.GetAdditionalInfo("batch-group") + re.True(ok) + re.Equal(batchGroup, opBatchGroup) +} + func TestDispatchSplitScatterKeepsStableGroupWhenRegionSplitsAgain(t *testing.T) { re := require.New(t) controller, tc, oc, cleanup := newTestSplitScatterController(t) @@ -724,6 +827,17 @@ func splitScatterPendingGroup(t *testing.T, controller *Controller, regionID uin return splitScatterPending(t, controller, regionID).group } +func splitScatterKeyspaceValidatorFor(keyspaces ...uint32) splitScatterKeyspaceValidator { + return func(keyspaceID uint32) bool { + for _, validKeyspaceID := range keyspaces { + if validKeyspaceID == keyspaceID { + return true + } + } + return false + } +} + func splitScatterPending(t *testing.T, controller *Controller, regionID uint64) splitScatterPendingItem { t.Helper() controller.splitScatter.pendingMu.RLock() @@ -786,18 +900,53 @@ func splitScatterIndexKeyPrefix() []byte { return codec.GenerateIndexKey(splitScatterTestTableID, splitScatterTestIndexID) } +func splitScatterKeyspacePrefixRange(keyspaceID uint32, rawPrefix []byte) splitScatterRangeHint { + startKey := newSplitScatterKeyspaceKey(keyspaceID, keyspace.TxnKeyspaceModePrefix, rawPrefix) + endRawPrefix := splitScatterNextPrefix(rawPrefix) + if len(endRawPrefix) == 0 { + return splitScatterRangeHint{startKey: startKey} + } + return splitScatterRangeHint{ + startKey: startKey, + endKey: newSplitScatterKeyspaceKey(keyspaceID, keyspace.TxnKeyspaceModePrefix, endRawPrefix), + } +} + func newSplitScatterIndexKey(suffix string) []byte { key := append([]byte(nil), splitScatterIndexKeyPrefix()...) key = append(key, suffix...) return codec.EncodeBytes(key) } +func newSplitScatterKeyspaceIndexKey(keyspaceID uint32, suffix string) []byte { + key := append([]byte(nil), splitScatterIndexKeyPrefix()...) + key = append(key, suffix...) + return newSplitScatterKeyspaceKey(keyspaceID, keyspace.TxnKeyspaceModePrefix, key) +} + func newSplitScatterRecordKey(tableID int64, suffix string) []byte { key := append([]byte(nil), codec.GenerateRecordKeyPrefix(tableID)...) key = append(key, suffix...) return codec.EncodeBytes(key) } +func newSplitScatterKeyspaceRecordKey(keyspaceID uint32, suffix string) []byte { + key := append([]byte(nil), codec.GenerateRecordKeyPrefix(splitScatterTestTableID)...) + key = append(key, suffix...) + return newSplitScatterKeyspaceKey(keyspaceID, keyspace.TxnKeyspaceModePrefix, key) +} + +func newSplitScatterRawKeyspaceRecordKey(keyspaceID uint32, tableID int64, suffix string) []byte { + key := append([]byte(nil), codec.GenerateRecordKeyPrefix(tableID)...) + key = append(key, suffix...) + return newSplitScatterKeyspaceKey(keyspaceID, keyspace.RawKeyspaceModePrefix, key) +} + func newSplitScatterTableBoundaryKey(tableID int64) []byte { return codec.EncodeBytes(codec.GenerateTableKey(tableID)) } + +func newSplitScatterKeyspaceKey(keyspaceID uint32, mode byte, rawKey []byte) []byte { + key := keyspace.MakeKeyspacePrefix(mode, keyspaceID) + return codec.EncodeBytes(append(key, rawKey...)) +}