5252import org .apache .doris .thrift .TWarmUpCacheAsyncRequest ;
5353import org .apache .doris .thrift .TWarmUpCacheAsyncResponse ;
5454
55+ import com .google .common .annotations .VisibleForTesting ;
5556import com .google .common .base .Preconditions ;
5657import com .google .common .base .Strings ;
5758import com .google .common .collect .Sets ;
7778import java .util .concurrent .LinkedBlockingQueue ;
7879import java .util .concurrent .ScheduledExecutorService ;
7980import java .util .concurrent .TimeUnit ;
81+ import java .util .function .Function ;
8082import java .util .stream .Collectors ;
8183
8284public class CloudTabletRebalancer extends MasterDaemon {
8385 private static final Logger LOG = LogManager .getLogger (CloudTabletRebalancer .class );
86+ private static final int MAX_GLOBAL_TABLET_SET_INITIAL_CAPACITY = 1 << 16 ;
87+ private static final Function <Long , Set <Long >> DEFAULT_GLOBAL_TABLET_SET_FACTORY =
88+ ignored -> ConcurrentHashMap .newKeySet ();
8489
8590 private final CloudTabletRebalancerMetrics rebalancerMetrics ;
8691 private long currentRoundTabletScanCount ;
@@ -318,7 +323,7 @@ public enum StatType {
318323 }
319324
320325 @ Getter
321- private class InfightTablet {
326+ private static class InfightTablet {
322327 private final long tabletId ;
323328 private final String clusterId ;
324329
@@ -341,7 +346,9 @@ public boolean equals(Object o) {
341346
342347 @ Override
343348 public int hashCode () {
344- return Objects .hash (tabletId , clusterId );
349+ int result = 1 ;
350+ result = 31 * result + Long .hashCode (tabletId );
351+ return 31 * result + clusterId .hashCode ();
345352 }
346353 }
347354
@@ -443,39 +450,50 @@ private class TransferPairInfo {
443450 }
444451
445452 public Set <Long > getSnapshotTabletsInPrimaryByBeId (Long beId ) {
446- Set <Long > tabletIds = Sets .newHashSet ();
447453 Set <Long > tablets = beToTabletsGlobal .get (beId );
448- if (tablets != null ) {
449- // Create a copy
450- tabletIds .addAll (new HashSet <>(tablets ));
451- }
452-
453454 Set <Long > colocateTablets = beToColocateTabletsGlobal .get (beId );
454- if (colocateTablets != null ) {
455- // Create a copy
456- tabletIds .addAll (new HashSet <>(colocateTablets ));
457- }
455+ Set <Long > tabletIds = newSnapshotTabletSet (tabletSetSize (tablets ) + tabletSetSize (colocateTablets ));
456+ addSnapshotTablets (tabletIds , tablets );
457+ addSnapshotTablets (tabletIds , colocateTablets );
458458
459459 return tabletIds ;
460460 }
461461
462462 public Set <Long > getSnapshotTabletsInSecondaryByBeId (Long beId ) {
463- Set <Long > tabletIds = Sets .newHashSet ();
464463 Set <Long > tablets = beToTabletsGlobalInSecondary .get (beId );
465- if (tablets != null ) {
466- // Create a copy
467- tabletIds .addAll (new HashSet <>(tablets ));
468- }
464+ Set <Long > tabletIds = newSnapshotTabletSet (tabletSetSize (tablets ));
465+ addSnapshotTablets (tabletIds , tablets );
469466 return tabletIds ;
470467 }
471468
472469 public Set <Long > getSnapshotTabletsInPrimaryAndSecondaryByBeId (Long beId ) {
473- Set <Long > tabletIds = Sets .newHashSet ();
474- tabletIds .addAll (getSnapshotTabletsInPrimaryByBeId (beId ));
475- tabletIds .addAll (getSnapshotTabletsInSecondaryByBeId (beId ));
470+ Set <Long > primaryTablets = beToTabletsGlobal .get (beId );
471+ Set <Long > colocateTablets = beToColocateTabletsGlobal .get (beId );
472+ Set <Long > secondaryTablets = beToTabletsGlobalInSecondary .get (beId );
473+ int expectedSize = tabletSetSize (primaryTablets )
474+ + tabletSetSize (colocateTablets ) + tabletSetSize (secondaryTablets );
475+ Set <Long > tabletIds = newSnapshotTabletSet (expectedSize );
476+ addSnapshotTablets (tabletIds , primaryTablets );
477+ addSnapshotTablets (tabletIds , colocateTablets );
478+ addSnapshotTablets (tabletIds , secondaryTablets );
476479 return tabletIds ;
477480 }
478481
482+ private static int tabletSetSize (Set <Long > tablets ) {
483+ return tablets == null ? 0 : tablets .size ();
484+ }
485+
486+ private static void addSnapshotTablets (Set <Long > snapshot , Set <Long > tablets ) {
487+ if (tablets != null ) {
488+ snapshot .addAll (tablets );
489+ }
490+ }
491+
492+ @ VisibleForTesting
493+ protected Set <Long > newSnapshotTabletSet (int expectedSize ) {
494+ return Sets .newHashSetWithExpectedSize (expectedSize );
495+ }
496+
479497 public int getTabletNumByBackendId (long beId ) {
480498 Map <Long , Set <Long >> sourceMap = beToTabletsGlobal ;
481499 ConcurrentHashMap <Long , Set <Long >> futureMap = futureBeToTabletsGlobal ;
@@ -954,34 +972,38 @@ private boolean completeRouteInfo() {
954972 long needRehashDeadTime = System .currentTimeMillis () - Config .rehash_tablet_after_be_dead_seconds * 1000L ;
955973 loopCloudReplica ((Database db , Table table , Partition partition , MaterializedIndex index , String cluster ) -> {
956974 boolean assigned = false ;
957- List <Long > beIds = new ArrayList <Long >();
958- List <Long > tabletIds = new ArrayList <Long >();
975+ List <Tablet > tablets = index .getTablets ();
959976 boolean isColocated = Env .getCurrentColocateIndex ().isColocateTable (table .getId ());
960- for (Tablet tablet : index .getTablets ()) {
977+ int routeCount = isColocated ? 0 : tablets .size ();
978+ List <Long > beIds = newRouteInfoList (routeCount );
979+ List <Long > tabletIds = newRouteInfoList (routeCount );
980+ for (Tablet tablet : tablets ) {
961981 for (Replica r : tablet .getReplicas ()) {
962982 CloudReplica replica = (CloudReplica ) r ;
963983 // clean secondary map
964984 replica .checkAndClearSecondaryClusterToBe (cluster , needRehashDeadTime );
965- InfightTablet taskKey = new InfightTablet (tablet .getId (), cluster );
966985 // colocate table no need to update primary backends
967986 if (isColocated ) {
968987 replica .clearClusterToBe (cluster );
969- tabletToInfightTask .remove (taskKey );
988+ tabletToInfightTask .remove (new InfightTablet ( tablet . getId (), cluster ) );
970989 continue ;
971990 }
972991
973992 // primary backend is alive or dead not long
974- Backend be = replica .getPrimaryBackend (cluster , false );
993+ Long primaryBeId = replica .getNonColocatedPrimaryBackendId (cluster );
994+ Backend be = primaryBeId == null
995+ ? null : Env .getCurrentSystemInfo ().getBackendByIdWithBoxedId (primaryBeId );
975996 if (be != null && (be .isQueryAvailable ()
976997 || (!be .isQueryDisabled ()
977998 // Compatible with older version upgrades, see https://github.com/apache/doris/pull/42986
978999 && (be .getLastUpdateMs () <= 0 || be .getLastUpdateMs () > needRehashDeadTime )))) {
979- beIds .add (be . getId () );
1000+ beIds .add (primaryBeId );
9801001 tabletIds .add (tablet .getId ());
9811002 continue ;
9821003 }
9831004
9841005 // primary backend not available too long, change one
1006+ InfightTablet taskKey = new InfightTablet (tablet .getId (), cluster );
9851007 long beId = -1L ;
9861008 be = replica .getSecondaryBackend (cluster );
9871009 if (be != null && be .isQueryAvailable ()) {
@@ -1043,14 +1065,37 @@ private boolean completeRouteInfo() {
10431065 return true ;
10441066 }
10451067
1068+ @ VisibleForTesting
1069+ protected <T > List <T > newRouteInfoList (int initialCapacity ) {
1070+ return new ArrayList <>(initialCapacity );
1071+ }
1072+
10461073 public void fillBeToTablets (long be , long tableId , long partId , long indexId , long tabletId ,
10471074 ConcurrentHashMap <Long , Set <Long >> globalBeToTablets ,
10481075 ConcurrentHashMap <Long , ConcurrentHashMap <Long , Set <Long >>> beToTabletsInTable ,
10491076 ConcurrentHashMap <Long , ConcurrentHashMap <Long , ConcurrentHashMap <Long , Set <Long >>>>
10501077 partToTablets ) {
1078+ fillBeToTablets (Long .valueOf (be ), Long .valueOf (tableId ), Long .valueOf (partId ), Long .valueOf (indexId ),
1079+ Long .valueOf (tabletId ), globalBeToTablets , beToTabletsInTable , partToTablets );
1080+ }
1081+
1082+ void fillBeToTablets (Long be , Long tableId , Long partId , Long indexId , Long tabletId ,
1083+ ConcurrentHashMap <Long , Set <Long >> globalBeToTablets ,
1084+ ConcurrentHashMap <Long , ConcurrentHashMap <Long , Set <Long >>> beToTabletsInTable ,
1085+ ConcurrentHashMap <Long , ConcurrentHashMap <Long , ConcurrentHashMap <Long , Set <Long >>>>
1086+ partToTablets ) {
1087+ fillBeToTablets (be , tableId , partId , indexId , tabletId , DEFAULT_GLOBAL_TABLET_SET_FACTORY ,
1088+ globalBeToTablets , beToTabletsInTable , partToTablets );
1089+ }
1090+
1091+ private void fillBeToTablets (Long be , Long tableId , Long partId , Long indexId , Long tabletId ,
1092+ Function <Long , Set <Long >> globalTabletSetFactory ,
1093+ ConcurrentHashMap <Long , Set <Long >> globalBeToTablets ,
1094+ ConcurrentHashMap <Long , ConcurrentHashMap <Long , Set <Long >>> beToTabletsInTable ,
1095+ ConcurrentHashMap <Long , ConcurrentHashMap <Long , ConcurrentHashMap <Long , Set <Long >>>>
1096+ partToTablets ) {
10511097 // global
1052- globalBeToTablets .putIfAbsent (be , ConcurrentHashMap .newKeySet ());
1053- globalBeToTablets .get (be ).add (tabletId );
1098+ globalBeToTablets .computeIfAbsent (be , globalTabletSetFactory ).add (tabletId );
10541099
10551100 // table
10561101 beToTabletsInTable .putIfAbsent (tableId , new ConcurrentHashMap <Long , Set <Long >>());
@@ -1067,6 +1112,23 @@ public void fillBeToTablets(long be, long tableId, long partId, long indexId, lo
10671112 beToTabletsOfIndex .get (be ).add (tabletId );
10681113 }
10691114
1115+ private Function <Long , Set <Long >> newGlobalTabletSetFactory (Map <Long , Set <Long >> previousBeToTablets ) {
1116+ Map <Long , Set <Long >> previousRoute = previousBeToTablets == null
1117+ ? Collections .emptyMap () : previousBeToTablets ;
1118+ return be -> {
1119+ Set <Long > previousTablets = previousRoute .get (be );
1120+ int initialCapacity = previousTablets == null ? 0
1121+ : Math .min (previousTablets .size (), MAX_GLOBAL_TABLET_SET_INITIAL_CAPACITY );
1122+ return newGlobalTabletSet (initialCapacity );
1123+ };
1124+ }
1125+
1126+ @ VisibleForTesting
1127+ protected Set <Long > newGlobalTabletSet (int initialCapacity ) {
1128+ return initialCapacity == 0
1129+ ? ConcurrentHashMap .newKeySet () : ConcurrentHashMap .newKeySet (initialCapacity );
1130+ }
1131+
10701132 private void enqueueWarmupTask (WarmupTabletTask task ) {
10711133 WarmupBatchKey key = new WarmupBatchKey (task .srcBe , task .destBe );
10721134 WarmupBatch batch = warmupBatches .computeIfAbsent (key , WarmupBatch ::new );
@@ -1142,6 +1204,12 @@ private void flushExpiredWarmupBatches() {
11421204 }
11431205
11441206 public void statRouteInfo () {
1207+ // The previous generation remains live until the temporary global routes are complete, so reuse its
1208+ // per-backend cardinalities as allocation hints without extending its lifetime.
1209+ Function <Long , Set <Long >> currentGlobalTabletSetFactory =
1210+ newGlobalTabletSetFactory (beToTabletsGlobal );
1211+ Function <Long , Set <Long >> futureGlobalTabletSetFactory =
1212+ newGlobalTabletSetFactory (futureBeToTabletsGlobal );
11451213 ConcurrentHashMap <Long , Set <Long >> tmpBeToTabletsGlobal = new ConcurrentHashMap <Long , Set <Long >>();
11461214 ConcurrentHashMap <Long , Set <Long >> tmpFutureBeToTabletsGlobal = new ConcurrentHashMap <Long , Set <Long >>();
11471215 ConcurrentHashMap <Long , Set <Long >> tmpBeToTabletsGlobalInSecondary
@@ -1166,25 +1234,31 @@ public void statRouteInfo() {
11661234 Map <Long , Boolean > tmpDbInternal = new HashMap <>();
11671235
11681236 loopCloudReplica ((Database db , Table table , Partition partition , MaterializedIndex index , String cluster ) -> {
1169- boolean isColocated = Env .getCurrentColocateIndex ().isColocateTable (table .getId ());
1170- tmpTableToDb .put (table .getId (), db .getId ());
1171- tmpPartitionToDb .put (partition .getId (), db .getId ());
1172- tmpDbInternal .computeIfAbsent (db .getId (), k -> {
1237+ Long dbId = db .getId ();
1238+ Long tableId = table .getId ();
1239+ Long partitionId = partition .getId ();
1240+ Long indexId = index .getId ();
1241+ boolean isColocated = Env .getCurrentColocateIndex ().isColocateTable (tableId );
1242+ tmpTableToDb .put (tableId , dbId );
1243+ tmpPartitionToDb .put (partitionId , dbId );
1244+ tmpDbInternal .computeIfAbsent (dbId , k -> {
11731245 String name = db .getFullName ();
11741246 return name != null && INTERNAL_DB_NAMES .contains (name );
11751247 });
11761248 for (Tablet tablet : index .getTablets ()) {
1177- long tabletId = tablet .getId ();
1249+ Long tabletId = tablet .getId ();
11781250 // active tablet scoring (used for scheduling order)
11791251 if (activeTabletIds != null && !activeTabletIds .isEmpty () && activeTabletIds .contains (tabletId )) {
1180- tmpTableActive .merge (table . getId () , 1L , Long ::sum );
1181- tmpPartitionActive .merge (partition . getId () , 1L , Long ::sum );
1182- tmpDbActive .merge (db . getId () , 1L , Long ::sum );
1252+ tmpTableActive .merge (tableId , 1L , Long ::sum );
1253+ tmpPartitionActive .merge (partitionId , 1L , Long ::sum );
1254+ tmpDbActive .merge (dbId , 1L , Long ::sum );
11831255 }
1184- for (Replica r : tablet .getReplicas ()) {
1185- CloudReplica replica = (CloudReplica ) r ;
1256+ List <Replica > replicas = tablet .getReplicas ();
1257+ int replicaCount = replicas .size ();
1258+ for (int replicaIndex = 0 ; replicaIndex < replicaCount ; replicaIndex ++) {
1259+ CloudReplica replica = (CloudReplica ) replicas .get (replicaIndex );
11861260 if (isColocated ) {
1187- long beId = -1L ;
1261+ Long beId = -1L ;
11881262 try {
11891263 beId = replica .getColocatedBeId (cluster );
11901264 } catch (ComputeGroupException e ) {
@@ -1198,27 +1272,32 @@ public void statRouteInfo() {
11981272 continue ;
11991273 }
12001274
1201- Backend be = replica .getPrimaryBackend (cluster , false );
1202- long beId = be == null ? -1L : be .getId ();
1275+ Long primaryBeId = replica .getNonColocatedPrimaryBackendId (cluster );
1276+ Backend be = primaryBeId == null
1277+ ? null : Env .getCurrentSystemInfo ().getBackendByIdWithBoxedId (primaryBeId );
1278+ Long beId = be == null ? Long .valueOf (-1L ) : primaryBeId ;
12031279 if (!allBes .contains (beId )) {
12041280 continue ;
12051281 }
12061282
12071283 Backend secondaryBe = replica .getSecondaryBackend (cluster );
1208- long secondaryBeId = secondaryBe == null ? -1L : secondaryBe .getId ();
1284+ Long secondaryBeId = secondaryBe == null ? Long . valueOf ( -1L ) : Long . valueOf ( secondaryBe .getId () );
12091285 if (allBes .contains (secondaryBeId )) {
12101286 Set <Long > tablets = tmpBeToTabletsGlobalInSecondary
12111287 .computeIfAbsent (secondaryBeId , k -> new HashSet <>());
12121288 tablets .add (tabletId );
12131289 }
12141290
1215- InfightTablet taskKey = new InfightTablet (tabletId , cluster );
1216- InfightTask task = tabletToInfightTask .get (taskKey );
1217- long futureBeId = task == null ? beId : task .destBe ;
1218- fillBeToTablets (beId , table .getId (), partition .getId (), index .getId (), tabletId ,
1291+ InfightTask task = tabletToInfightTask .isEmpty () ? null
1292+ : tabletToInfightTask .get (new InfightTablet (tabletId , cluster ));
1293+ Long futureBeId = task == null ? beId : Long .valueOf (task .destBe );
1294+ Long routeTabletId = task == null ? tabletId : task .pickedTabletId ;
1295+ fillBeToTablets (beId , tableId , partitionId , indexId , routeTabletId ,
1296+ currentGlobalTabletSetFactory ,
12191297 tmpBeToTabletsGlobal , beToTabletsInTable , this .partitionToTablets );
12201298
1221- fillBeToTablets (futureBeId , table .getId (), partition .getId (), index .getId (), tabletId ,
1299+ fillBeToTablets (futureBeId , tableId , partitionId , indexId , routeTabletId ,
1300+ futureGlobalTabletSetFactory ,
12221301 tmpFutureBeToTabletsGlobal , futureBeToTabletsInTable , futurePartitionToTablets );
12231302 }
12241303 }
0 commit comments