Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import org.apache.doris.thrift.TWarmUpCacheAsyncRequest;
import org.apache.doris.thrift.TWarmUpCacheAsyncResponse;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
Expand All @@ -77,10 +78,14 @@
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

public class CloudTabletRebalancer extends MasterDaemon {
private static final Logger LOG = LogManager.getLogger(CloudTabletRebalancer.class);
private static final int MAX_GLOBAL_TABLET_SET_INITIAL_CAPACITY = 1 << 20;
private static final Function<Long, Set<Long>> DEFAULT_GLOBAL_TABLET_SET_FACTORY =
ignored -> ConcurrentHashMap.newKeySet();

private volatile ConcurrentHashMap<Long, Set<Long>> beToTabletsGlobal =
new ConcurrentHashMap<Long, Set<Long>>();
Expand Down Expand Up @@ -1067,8 +1072,18 @@ void fillBeToTablets(Long be, Long tableId, Long partId, Long indexId, Long tabl
ConcurrentHashMap<Long, ConcurrentHashMap<Long, Set<Long>>> beToTabletsInTable,
ConcurrentHashMap<Long, ConcurrentHashMap<Long, ConcurrentHashMap<Long, Set<Long>>>>
partToTablets) {
fillBeToTablets(be, tableId, partId, indexId, tabletId, DEFAULT_GLOBAL_TABLET_SET_FACTORY,
globalBeToTablets, beToTabletsInTable, partToTablets);
}

private void fillBeToTablets(Long be, Long tableId, Long partId, Long indexId, Long tabletId,
Function<Long, Set<Long>> globalTabletSetFactory,
ConcurrentHashMap<Long, Set<Long>> globalBeToTablets,
ConcurrentHashMap<Long, ConcurrentHashMap<Long, Set<Long>>> beToTabletsInTable,
ConcurrentHashMap<Long, ConcurrentHashMap<Long, ConcurrentHashMap<Long, Set<Long>>>>
partToTablets) {
// global
globalBeToTablets.computeIfAbsent(be, ignored -> ConcurrentHashMap.newKeySet()).add(tabletId);
globalBeToTablets.computeIfAbsent(be, globalTabletSetFactory).add(tabletId);

// table
ConcurrentHashMap<Long, Set<Long>> beToTabletsOfTable =
Expand All @@ -1083,6 +1098,23 @@ void fillBeToTablets(Long be, Long tableId, Long partId, Long indexId, Long tabl
beToTabletsOfIndex.computeIfAbsent(be, ignored -> ConcurrentHashMap.newKeySet()).add(tabletId);
}

private Function<Long, Set<Long>> newGlobalTabletSetFactory(Map<Long, Set<Long>> previousBeToTablets) {
Map<Long, Set<Long>> previousRoute = previousBeToTablets == null
? Collections.emptyMap() : previousBeToTablets;
return be -> {
Set<Long> previousTablets = previousRoute.get(be);
int initialCapacity = previousTablets == null ? 0
: Math.min(previousTablets.size(), MAX_GLOBAL_TABLET_SET_INITIAL_CAPACITY);
return newGlobalTabletSet(initialCapacity);
};
}

@VisibleForTesting
protected Set<Long> newGlobalTabletSet(int initialCapacity) {
return initialCapacity == 0
? ConcurrentHashMap.newKeySet() : ConcurrentHashMap.newKeySet(initialCapacity);
}

private void enqueueWarmupTask(WarmupTabletTask task) {
WarmupBatchKey key = new WarmupBatchKey(task.srcBe, task.destBe);
WarmupBatch batch = warmupBatches.computeIfAbsent(key, WarmupBatch::new);
Expand Down Expand Up @@ -1158,6 +1190,12 @@ private void flushExpiredWarmupBatches() {
}

public void statRouteInfo() {
// The previous generation remains live until the temporary global routes are complete, so reuse its
// per-backend cardinalities as allocation hints without extending its lifetime.
Function<Long, Set<Long>> currentGlobalTabletSetFactory =
newGlobalTabletSetFactory(beToTabletsGlobal);
Function<Long, Set<Long>> futureGlobalTabletSetFactory =
newGlobalTabletSetFactory(futureBeToTabletsGlobal);
ConcurrentHashMap<Long, Set<Long>> tmpBeToTabletsGlobal = new ConcurrentHashMap<Long, Set<Long>>();
ConcurrentHashMap<Long, Set<Long>> tmpFutureBeToTabletsGlobal = new ConcurrentHashMap<Long, Set<Long>>();
ConcurrentHashMap<Long, Set<Long>> tmpBeToTabletsGlobalInSecondary
Expand Down Expand Up @@ -1237,9 +1275,11 @@ public void statRouteInfo() {
Long futureBeId = task == null ? beId : Long.valueOf(task.destBe);
Long routeTabletId = task == null ? tabletId : task.pickedTabletId;
fillBeToTablets(beId, tableId, partitionId, indexId, routeTabletId,
currentGlobalTabletSetFactory,
tmpBeToTabletsGlobal, beToTabletsInTable, this.partitionToTablets);

fillBeToTablets(futureBeId, tableId, partitionId, indexId, routeTabletId,
futureGlobalTabletSetFactory,
tmpFutureBeToTabletsGlobal, futureBeToTabletsInTable, futurePartitionToTablets);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@ protected boolean isInternalDbId(Long dbId) {
}
}

private static class CapacityTrackingRebalancer extends TestRebalancer {
private final List<Integer> globalTabletSetInitialCapacities = new ArrayList<>();

@Override
protected Set<Long> newGlobalTabletSet(int initialCapacity) {
globalTabletSetInitialCapacities.add(initialCapacity);
return super.newGlobalTabletSet(initialCapacity);
}
}

private static class CountingConcurrentHashMap<K, V> extends ConcurrentHashMap<K, V> {
private int computeIfAbsentCalls;
private int getCalls;
Expand Down Expand Up @@ -349,6 +359,96 @@ public void testWarmupRollbackReusesInflightBoxedTabletIdAfterRouteRebuild() thr
}
}

@Test
public void testStatRouteInfoPresizesGlobalTabletSetsFromPreviousRoute() throws Exception {
CapacityTrackingRebalancer rebalancer = new CapacityTrackingRebalancer();
Long dbId = 10_001L;
Long tableId = 20_001L;
Long partitionId = 30_001L;
Long indexId = 40_001L;
Long tabletId = 50_001L;
Long beId = 60_001L;
String clusterId = "cluster-a";

ConcurrentHashMap<Long, Set<Long>> previousCurrent = new ConcurrentHashMap<>();
previousCurrent.put(beId, Set.of(1L, 2L, 3L));
ConcurrentHashMap<Long, Set<Long>> previousFuture = new ConcurrentHashMap<>();
previousFuture.put(beId, Set.of(1L, 2L, 3L, 4L, 5L));
setField(rebalancer, "beToTabletsGlobal", previousCurrent);
setField(rebalancer, "futureBeToTabletsGlobal", previousFuture);
setField(rebalancer, "clusterToBes", Collections.singletonMap(clusterId, List.of(beId)));
setField(rebalancer, "allBes", Set.of(beId));

try (MockedStatic<Env> ignored = mockRouteEnvironment(
dbId, tableId, partitionId, indexId, tabletId, clusterId, beId)) {
rebalancer.statRouteInfo();
}

Assertions.assertEquals(List.of(3, 5), rebalancer.globalTabletSetInitialCapacities);
ConcurrentHashMap<Long, Set<Long>> current = getField(rebalancer, "beToTabletsGlobal");
ConcurrentHashMap<Long, Set<Long>> future = getField(rebalancer, "futureBeToTabletsGlobal");
Assertions.assertEquals(Set.of(tabletId), current.get(beId));
Assertions.assertEquals(Set.of(tabletId), future.get(beId));
}

@Test
@SuppressWarnings("unchecked")
public void testStatRouteInfoBoundsStaleGlobalTabletSetCapacity() throws Exception {
CapacityTrackingRebalancer rebalancer = new CapacityTrackingRebalancer();
Long dbId = 10_001L;
Long tableId = 20_001L;
Long partitionId = 30_001L;
Long indexId = 40_001L;
Long tabletId = 50_001L;
Long beId = 60_001L;
String clusterId = "cluster-a";

Set<Long> stalePreviousTablets = Mockito.mock(Set.class);
Mockito.when(stalePreviousTablets.size()).thenReturn(2_000_000);
ConcurrentHashMap<Long, Set<Long>> previousCurrent = new ConcurrentHashMap<>();
previousCurrent.put(beId, stalePreviousTablets);
ConcurrentHashMap<Long, Set<Long>> previousFuture = new ConcurrentHashMap<>();
previousFuture.put(beId, stalePreviousTablets);
setField(rebalancer, "beToTabletsGlobal", previousCurrent);
setField(rebalancer, "futureBeToTabletsGlobal", previousFuture);
setField(rebalancer, "clusterToBes", Collections.singletonMap(clusterId, List.of(beId)));
setField(rebalancer, "allBes", Set.of(beId));

try (MockedStatic<Env> ignored = mockRouteEnvironment(
dbId, tableId, partitionId, indexId, tabletId, clusterId, beId)) {
rebalancer.statRouteInfo();
}

Assertions.assertEquals(List.of(1_048_576, 1_048_576),
rebalancer.globalTabletSetInitialCapacities);
ConcurrentHashMap<Long, Set<Long>> current = getField(rebalancer, "beToTabletsGlobal");
ConcurrentHashMap<Long, Set<Long>> future = getField(rebalancer, "futureBeToTabletsGlobal");
Assertions.assertEquals(Set.of(tabletId), current.get(beId));
Assertions.assertEquals(Set.of(tabletId), future.get(beId));
}

@Test
public void testStatRouteInfoUsesZeroCapacityForNewBackend() throws Exception {
CapacityTrackingRebalancer rebalancer = new CapacityTrackingRebalancer();
Long dbId = 10_001L;
Long tableId = 20_001L;
Long partitionId = 30_001L;
Long indexId = 40_001L;
Long tabletId = 50_001L;
Long beId = 60_001L;
String clusterId = "cluster-a";

setField(rebalancer, "clusterToBes", Collections.singletonMap(clusterId, List.of(beId)));
setField(rebalancer, "allBes", Set.of(beId));

try (MockedStatic<Env> ignored = mockRouteEnvironment(
dbId, tableId, partitionId, indexId, tabletId, clusterId, beId)) {
rebalancer.statRouteInfo();
}

Assertions.assertEquals(List.of(0, 0), rebalancer.globalTabletSetInitialCapacities);
}

private static void initializeRouteMaps(TestRebalancer rebalancer, RouteMaps current, RouteMaps future,
Long srcBe, Long tableId, Long partitionId, Long indexId, Long tabletId) throws Exception {
rebalancer.fillBeToTablets(srcBe, tableId, partitionId, indexId, tabletId,
Expand Down
Loading