Skip to content

Commit fcd443e

Browse files
authored
[improvement](fe) Presize global cloud tablet route sets (#66447) (#67283)
pick from #66447 Presize cloud tablet route sets and reuse boxed route identifiers during rebuilds. (cherry picked from commit 634cbaa) ### What problem does this PR solve? Issue Number: close #xxx Related PR: #xxx Problem Summary: ### Release note None ### Check List (For Author) - Test <!-- At least one of them must be included. --> - [ ] Regression test - [ ] Unit Test - [ ] Manual test (add detailed scripts or steps below) - [ ] No need to test or manual test. Explain why: - [ ] This is a refactor/code format and no logic has been changed. - [ ] Previous test can cover this change. - [ ] No code files have been changed. - [ ] Other reason <!-- Add your reason? --> - Behavior changed: - [ ] No. - [ ] Yes. <!-- Explain the behavior change --> - Does this need documentation? - [ ] No. - [ ] Yes. <!-- Add document PR link here. eg: apache/doris-website#1214 --> ### Check List (For Reviewer who merge this PR) - [ ] Confirm the release note - [ ] Confirm test cases - [ ] Confirm document - [ ] Add branch pick label <!-- Add branch pick label that this PR should merge into -->
1 parent 827e388 commit fcd443e

3 files changed

Lines changed: 135 additions & 48 deletions

File tree

fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudReplica.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,10 @@ public long getClusterPrimaryBackendId(String clusterId) {
284284
return primaryClusterToBackend.getOrDefault(clusterId, -1L);
285285
}
286286

287+
Long getNonColocatedPrimaryBackendId(String clusterId) {
288+
return primaryClusterToBackend.get(clusterId);
289+
}
290+
287291
// For proc display only. In cloud mode a replica is hashed to a different BE in each
288292
// compute group, so expose a clusterId -> backendId mapping; the proc display builds
289293
// a separate bucket sequence per compute group from it so each group's sequence is

fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudTabletRebalancer.java

Lines changed: 127 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.apache.doris.thrift.TWarmUpCacheAsyncRequest;
5353
import org.apache.doris.thrift.TWarmUpCacheAsyncResponse;
5454

55+
import com.google.common.annotations.VisibleForTesting;
5556
import com.google.common.base.Preconditions;
5657
import com.google.common.base.Strings;
5758
import com.google.common.collect.Sets;
@@ -77,10 +78,14 @@
7778
import java.util.concurrent.LinkedBlockingQueue;
7879
import java.util.concurrent.ScheduledExecutorService;
7980
import java.util.concurrent.TimeUnit;
81+
import java.util.function.Function;
8082
import java.util.stream.Collectors;
8183

8284
public 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
}

fe/fe-core/src/main/java/org/apache/doris/system/SystemInfoService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,10 @@ public Backend getBackend(long backendId) {
336336
return getAllClusterBackendsNoException().get(backendId);
337337
}
338338

339+
public Backend getBackendByIdWithBoxedId(Long backendId) {
340+
return getAllClusterBackendsNoException().get(backendId);
341+
}
342+
339343
public List<Backend> getBackends(List<Long> backendIds) {
340344
List<Backend> backends = Lists.newArrayList();
341345
for (long backendId : backendIds) {

0 commit comments

Comments
 (0)