Skip to content

Commit 5c811b2

Browse files
authored
tso: keyspace group auto splitting (#10932)
close #10933\n\nThe TSO keyspace group supports checking the keyspace count automatically every 15 minutes and triggering a split if a certain threshold is reached, preventing the corresponding etcd value from becoming too long.\n\nSigned-off-by: ystaticy <y_static_y@sina.com>
1 parent 52c2882 commit 5c811b2

2 files changed

Lines changed: 613 additions & 0 deletions

File tree

pkg/keyspace/tso_keyspace_group.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ const (
5353
allocNodesToKeyspaceGroupsInterval = 1 * time.Second
5454
allocNodesTimeout = 1 * time.Second
5555
allocNodesInterval = 10 * time.Millisecond
56+
// defaultKeyspaceCountSplitThreshold is the keyspace count threshold for auto-splitting
57+
// a keyspace group. When a group's keyspace count exceeds this value, a new group will be split automatically.
58+
defaultKeyspaceCountSplitThreshold = 40000
59+
// autoSplitKeyspaceGroupPatrolInterval is the interval for patrolling keyspace group size for auto-split.
60+
autoSplitKeyspaceGroupPatrolInterval = 15 * time.Minute
5661
)
5762

5863
const (
@@ -151,6 +156,8 @@ func (m *GroupManager) Bootstrap(ctx context.Context) error {
151156
if m.client != nil {
152157
m.wg.Add(1)
153158
go m.allocNodesToAllKeyspaceGroups(ctx)
159+
m.wg.Add(1)
160+
go m.patrolKeyspaceGroupSizeForAutoSplit(ctx)
154161
}
155162
return nil
156163
}
@@ -221,6 +228,133 @@ func (m *GroupManager) allocNodesToAllKeyspaceGroups(ctx context.Context) {
221228
}
222229
}
223230

231+
// patrolKeyspaceGroupSizeForAutoSplit periodically checks all tso keyspace groups.
232+
// If a group's keyspace count exceeds defaultKeyspaceCountSplitThreshold,
233+
// it automatically splits a new group and moves about half of the keyspaces to the new group.
234+
func (m *GroupManager) patrolKeyspaceGroupSizeForAutoSplit(ctx context.Context) {
235+
defer logutil.LogPanic()
236+
defer m.wg.Done()
237+
ticker := time.NewTicker(autoSplitKeyspaceGroupPatrolInterval)
238+
defer ticker.Stop()
239+
log.Info("start to patrol keyspace group size for auto-split")
240+
for {
241+
select {
242+
case <-m.ctx.Done():
243+
log.Info("server is closed, stop patrolling keyspace group size for auto-split")
244+
return
245+
case <-ctx.Done():
246+
log.Info("the raftcluster is closed, stop patrolling keyspace group size for auto-split")
247+
return
248+
case <-ticker.C:
249+
m.doPatrolKeyspaceGroupSizeForAutoSplit(ctx)
250+
}
251+
}
252+
}
253+
254+
// doPatrolKeyspaceGroupSizeForAutoSplit checks once and performs at most one auto-split.
255+
func (m *GroupManager) doPatrolKeyspaceGroupSizeForAutoSplit(ctx context.Context) {
256+
select {
257+
case <-m.ctx.Done():
258+
return
259+
case <-ctx.Done():
260+
return
261+
default:
262+
}
263+
groups, err := m.store.LoadKeyspaceGroups(constant.DefaultKeyspaceGroupID, 0)
264+
if err != nil {
265+
log.Error("auto-split patrol failed to load all keyspace groups",
266+
zap.Error(err))
267+
return
268+
}
269+
if len(groups) == 0 {
270+
return
271+
}
272+
nextID, ok := findNextAvailableKeyspaceGroupID(groups, mcs.MaxKeyspaceGroupCountInUse)
273+
if !ok {
274+
log.Warn("no available keyspace group id for auto-split, max id reached",
275+
zap.Uint32("max-keyspace-group-count-in-use", mcs.MaxKeyspaceGroupCountInUse))
276+
return
277+
}
278+
threshold := defaultKeyspaceCountSplitThreshold
279+
failpoint.Inject("autoSplitKeyspaceGroupThreshold", func() {
280+
threshold = 5
281+
})
282+
283+
sortedGroups := make([]*endpoint.KeyspaceGroup, len(groups))
284+
copy(sortedGroups, groups)
285+
sort.Slice(sortedGroups, func(i, j int) bool {
286+
if len(sortedGroups[i].Keyspaces) == len(sortedGroups[j].Keyspaces) {
287+
return sortedGroups[i].ID < sortedGroups[j].ID
288+
}
289+
return len(sortedGroups[i].Keyspaces) > len(sortedGroups[j].Keyspaces)
290+
})
291+
292+
for _, group := range sortedGroups {
293+
if group.IsSplitting() || group.IsMerging() {
294+
continue
295+
}
296+
if len(group.Members) < mcs.DefaultKeyspaceGroupReplicaCount {
297+
continue
298+
}
299+
count := len(group.Keyspaces)
300+
if count <= threshold {
301+
continue
302+
}
303+
// Split about half of the keyspaces to the new group (excluding protected bootstrap/system keyspace).
304+
splitIdx := count / 2
305+
keyspacesToMove := make([]uint32, 0, count-splitIdx)
306+
for i := splitIdx; i < count; i++ {
307+
kid := group.Keyspaces[i]
308+
if isProtectedKeyspaceID(kid) {
309+
continue
310+
}
311+
keyspacesToMove = append(keyspacesToMove, kid)
312+
}
313+
if len(keyspacesToMove) == 0 {
314+
log.Warn("no keyspaces to move for auto-split, skip",
315+
zap.Uint32("keyspace-group-id", group.ID),
316+
zap.Int("keyspace-count", count))
317+
continue
318+
}
319+
err := m.SplitKeyspaceGroupByID(group.ID, nextID, keyspacesToMove)
320+
if err != nil {
321+
log.Error("failed to auto-split keyspace group by keyspace count",
322+
zap.Uint32("source-id", group.ID),
323+
zap.Uint32("target-id", nextID),
324+
zap.Int("keyspace-count", count),
325+
zap.Int("keyspaces-to-move", len(keyspacesToMove)),
326+
zap.Error(err))
327+
return
328+
}
329+
log.Info("auto-split keyspace group by keyspace count",
330+
zap.Uint32("source-id", group.ID),
331+
zap.Uint32("target-id", nextID),
332+
zap.Int("keyspace-count", count),
333+
zap.Int("keyspaces-moved", len(keyspacesToMove)))
334+
return
335+
}
336+
}
337+
338+
// findNextAvailableKeyspaceGroupID returns the smallest unused keyspace group ID in
339+
// [1, maxCount). Group 0 is reserved for the default keyspace group.
340+
func findNextAvailableKeyspaceGroupID(groups []*endpoint.KeyspaceGroup, maxCount uint32) (uint32, bool) {
341+
if maxCount <= 1 {
342+
return 0, false
343+
}
344+
used := make(map[uint32]struct{}, len(groups))
345+
for _, g := range groups {
346+
if g.ID < maxCount {
347+
used[g.ID] = struct{}{}
348+
}
349+
}
350+
for id := uint32(1); id < maxCount; id++ {
351+
if _, exists := used[id]; !exists {
352+
return id, true
353+
}
354+
}
355+
return 0, false
356+
}
357+
224358
func (m *GroupManager) initTSONodesWatcher(client *clientv3.Client) {
225359
putFn := func(kv *mvccpb.KeyValue) error {
226360
s := &discovery.ServiceRegistryEntry{}

0 commit comments

Comments
 (0)