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 @@ -31,6 +31,8 @@
import org.apache.doris.system.SystemInfoService;

import com.google.common.base.Strings;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import com.google.gson.annotations.SerializedName;
Expand All @@ -52,6 +54,10 @@
public class CloudReplica extends Replica implements GsonPostProcessable {
private static final Logger LOG = LogManager.getLogger(CloudReplica.class);

// Routes keep shared objects alive; weak interners release IDs after their last route disappears.
private static final Interner<String> SHARED_CLUSTER_IDS = Interners.newWeakInterner();
private static final Interner<Long> SHARED_BACKEND_IDS = Interners.newWeakInterner();

// a replica is mapped to one BE in a cluster, use primaryClusterToBackend instead of primaryClusterToBackends
@Deprecated
@SerializedName(value = "bes")
Expand Down Expand Up @@ -600,8 +606,16 @@ public long getIdx() {
return idx;
}

private static String shareClusterId(String clusterId) {
return SHARED_CLUSTER_IDS.intern(clusterId);
}

private static Long shareBackendId(Long backendId) {
return SHARED_BACKEND_IDS.intern(backendId);
}

public void updateClusterToPrimaryBe(String cluster, long beId) {
primaryClusterToBackend.put(cluster, beId);
primaryClusterToBackend.put(shareClusterId(cluster), shareBackendId(beId));
secondaryClusterToBackends.remove(cluster);
}

Expand All @@ -616,7 +630,7 @@ public void updateClusterToSecondaryBe(String cluster, long beId) {
LOG.debug("add to secondary clusterId {}, beId {}, changeTimestamp {}, replica info {}",
cluster, beId, changeTimestamp, this);
}
secondaryClusterToBackends.put(cluster, Pair.of(beId, changeTimestamp));
secondaryClusterToBackends.put(shareClusterId(cluster), Pair.of(beId, changeTimestamp));
}

public void clearClusterToBe(String cluster) {
Expand Down Expand Up @@ -744,5 +758,10 @@ public void gsonPostProcess() throws IOException {
// checkpoint thread resolves Env.getCurrentEnv() to its own Env, so the backend set read here is
// the one belonging to the image being loaded.
removeInvalidRoutes();

ConcurrentHashMap<String, Long> sharedRoutes = new ConcurrentHashMap<>(primaryClusterToBackend.size());
primaryClusterToBackend.forEach((clusterId, backendId) ->
sharedRoutes.put(shareClusterId(clusterId), shareBackendId(backendId)));
primaryClusterToBackend = sharedRoutes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,20 @@
import org.apache.doris.cloud.qe.ComputeGroupException;
import org.apache.doris.cloud.system.CloudSystemInfoService;
import org.apache.doris.common.Config;
import org.apache.doris.persist.gson.GsonUtils;
import org.apache.doris.system.Backend;

import com.google.common.testing.GcFinalization;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand Down Expand Up @@ -343,4 +348,79 @@ public void testGetAllPrimaryBes_colocateTable_emptyClusterList() {
List<Backend> primaryBes = replica.getAllPrimaryBes();
Assertions.assertTrue(primaryBes.isEmpty());
}

private void stubBackend(long id, Backend be) {
Mockito.when(mockInfoService.getBackend(id)).thenReturn(be);
Mockito.when(mockInfoService.getBackendByIdWithBoxedId(id)).thenReturn(be);
}

private CloudReplica gsonRoundTrip(CloudReplica replica, boolean legacyBesFormat) {
JsonObject json = GsonUtils.GSON.toJsonTree(replica, Replica.class).getAsJsonObject();
if (legacyBesFormat) {
JsonObject be = json.remove("be").getAsJsonObject();
JsonObject bes = new JsonObject();
for (String computeGroup : be.keySet()) {
JsonArray beIds = new JsonArray();
beIds.add(be.get(computeGroup).getAsLong());
bes.add(computeGroup, beIds);
}
json.add("bes", bes);
}
return (CloudReplica) GsonUtils.GSON.fromJson(json, Replica.class);
}

private void assertPrimaryRouteObjectsShared(CloudReplica first, CloudReplica second) {
Assertions.assertSame(first.getPrimaryComputeGroupIds().iterator().next(),
second.getPrimaryComputeGroupIds().iterator().next());
Assertions.assertSame(first.getNonColocatedPrimaryBackendId(CLUSTER_ID_1),
second.getNonColocatedPrimaryBackendId(CLUSTER_ID_1));
}

@Test
public void testRouteObjectsSharedOnUpdate() {
CloudReplica first = createReplica();
CloudReplica second = createReplica();

first.updateClusterToPrimaryBe(new String(CLUSTER_ID_1), 1001L);
second.updateClusterToPrimaryBe(new String(CLUSTER_ID_1), 1001L);

assertPrimaryRouteObjectsShared(first, second);
}

@Test
public void testUnusedRouteObjectsCanBeCollected() {
List<WeakReference<?>> references = createAndClearRouteReferences();

references.forEach(GcFinalization::awaitClear);
}

private List<WeakReference<?>> createAndClearRouteReferences() {
String clusterId = new String("collectable_cluster_id");
long backendId = 9_000_000_000_001L;
CloudReplica replica = createReplica();
replica.updateClusterToPrimaryBe(clusterId, backendId);
WeakReference<String> clusterReference = new WeakReference<>(
replica.getPrimaryComputeGroupIds().iterator().next());
WeakReference<Long> backendReference = new WeakReference<>(
replica.getNonColocatedPrimaryBackendId(clusterId));

replica.clearClusterToBe(clusterId);
return Arrays.asList(clusterReference, backendReference);
}

@Test
public void testRouteObjectsSharedOnImageLoad() {
stubBackend(1001L, createBackend(1001L, true, false));
CloudReplica replica = createReplica();
replica.updateClusterToPrimaryBe(CLUSTER_ID_1, 1001L);
JsonObject originalJson = GsonUtils.GSON.toJsonTree(replica, Replica.class).getAsJsonObject();

CloudReplica first = gsonRoundTrip(replica, false);
CloudReplica second = gsonRoundTrip(replica, false);
assertPrimaryRouteObjectsShared(first, second);
Assertions.assertEquals(originalJson,
GsonUtils.GSON.toJsonTree(first, Replica.class).getAsJsonObject());

assertPrimaryRouteObjectsShared(gsonRoundTrip(replica, true), gsonRoundTrip(replica, true));
}
}
Loading