@@ -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
5863const (
@@ -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+
224358func (m * GroupManager ) initTSONodesWatcher (client * clientv3.Client ) {
225359 putFn := func (kv * mvccpb.KeyValue ) error {
226360 s := & discovery.ServiceRegistryEntry {}
0 commit comments