This is an automated email from the ASF dual-hosted git repository.
kfaraz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new 8d9545ea585 feat: Define clone sync criteria in
CoordinatorDynamicConfig (#20247)
8d9545ea585 is described below
commit 8d9545ea585a5d49cdd27384b4a3b365f1e30dd7
Author: Kashif Faraz <[email protected]>
AuthorDate: Thu Sep 10 10:37:08 2026 +0530
feat: Define clone sync criteria in CoordinatorDynamicConfig (#20247)
When cloning historicals for a blue-green deployment using
`CoordinatorDynamicConfig.cloneServers`,
it can often be difficult to determine if the clones are already synced and
can be safely terminated.
This is primarily because the source server keeps getting assigned new
segments and the clone target
is always playing catch up.
The metric that has been used so far to determine if a clone is
sufficiently "synced" is the
`segmentLoadsRemaining` returned by the `/cloneStatus` API, but that number
often fails to fall below
any reasonable threshold thereby slowing down the deployment.
Changes
---------
- Add a configurable `CloneSyncCriteria` to `CoordinatorDynamicConfig`
- The criteria is a function of segments "pending sync" during a
coordinator run i.e. segments which
are already loaded on the source server but still loading at the target
server at the start of the run.
- If the criteria is met, update the state of the clone to `SYNCED` and
emit a metric
- In subsequent runs, it is possible for the state to go back to
`IN_PROGRESS` if the clone starts lagging
behind again.
- Add new metrics to track number of segments pending sync and the synced
status of a clone
---
docs/api-reference/dynamic-configuration-api.md | 7 +-
docs/configuration/index.md | 3 +
docs/operations/metrics.md | 5 +
.../org/apache/druid/common/config/Configs.java | 9 ++
.../apache/druid/common/config/ConfigsTest.java | 5 +-
.../server/coordinator/CloneSyncCriteria.java | 75 ++++++++++++
.../coordinator/CoordinatorDynamicConfig.java | 29 +++++
.../server/coordinator/ServerCloneStatus.java | 134 ++++-----------------
.../server/coordinator/duty/CloneHistoricals.java | 105 +++++++++++++++-
.../druid/server/coordinator/stats/Stats.java | 4 +
.../CoordinatorDynamicConfigTest.java | 6 +-
.../server/coordinator/ServerCloneStatusTest.java | 11 +-
.../simulate/CoordinatorSimulation.java | 8 ++
.../simulate/CoordinatorSimulationBaseTest.java | 9 ++
.../simulate/CoordinatorSimulationBuilder.java | 9 ++
.../simulate/HistoricalCloningTest.java | 61 ++++++++++
.../CoordinatorDynamicConfigsResourceTest.java | 2 +-
17 files changed, 359 insertions(+), 123 deletions(-)
diff --git a/docs/api-reference/dynamic-configuration-api.md
b/docs/api-reference/dynamic-configuration-api.md
index fefbc22c313..94c8c52beb3 100644
--- a/docs/api-reference/dynamic-configuration-api.md
+++ b/docs/api-reference/dynamic-configuration-api.md
@@ -108,6 +108,7 @@ Host: http://ROUTER_IP:ROUTER_PORT
"debugDimensions": null,
"turboLoadingNodes": [],
"cloneServers": {},
+ "cloneSyncCriteria": {},
"historicalTierAliases": {
"hot": ["hot_1", "hot_2"]
}
@@ -180,7 +181,8 @@ curl
"http://ROUTER_IP:ROUTER_PORT/druid/coordinator/v1/config" \
"maxNonPrimaryReplicantsToLoad": 2147483647,
"useRoundRobinSegmentAssignment": true,
"turboLoadingNodes": [],
- "cloneServers": {}
+ "cloneServers": {},
+ "cloneSyncCriteria": {}
}'
```
@@ -213,7 +215,8 @@ Content-Length: 683
"maxNonPrimaryReplicantsToLoad": 2147483647,
"useRoundRobinSegmentAssignment": true,
"turboLoadingNodes": [],
- "cloneServers": {}
+ "cloneServers": {},
+ "cloneSyncCriteria": {}
}
```
diff --git a/docs/configuration/index.md b/docs/configuration/index.md
index c4def258739..d07756b858e 100644
--- a/docs/configuration/index.md
+++ b/docs/configuration/index.md
@@ -805,6 +805,9 @@ The following table shows the dynamic configuration
properties for the Coordinat
|`replicateAfterLoadTimeout`|Boolean flag for whether or not additional
replication is needed for segments that have failed to load due to the expiry
of `druid.coordinator.load.timeout`. If this is set to true, the Coordinator
will attempt to replicate the failed segment on a different historical server.
This helps improve the segment availability if there are a few slow Historicals
in the cluster. However, the slow Historical may still load the segment later
and the Coordinator may issu [...]
|`turboLoadingNodes`| Experimental. List of Historical servers to place in
turbo loading mode. These servers use a larger thread-pool to load segments
faster but at the cost of query performance. For servers specified in
`turboLoadingNodes`, `druid.coordinator.loadqueuepeon.http.batchSize` is
ignored and the coordinator uses the value of the respective
`numLoadingThreads` instead.<br/>Please use this config with caution. All
servers should eventually be removed from this list once the se [...]
|`cloneServers`| Experimental. Map from target Historical server to source
Historical server which should be cloned by the target. The target Historical
does not participate in regular segment assignment or balancing. Instead, the
Coordinator mirrors any segment assignment made to the source Historical onto
the target Historical, so that the target becomes an exact copy of the source.
Segments on the target Historical do not count towards replica counts either.
If the source disappears, [...]
+|`cloneSyncCriteria`| Experimental. Criteria defining when a clone historical
should be considered as "synced" to its source server. The criteria is a
function of the number and percentage of segments "pending sync", i.e. segments
already loaded on the source server but still loading on the target server.
Segments which are yet to be loaded on the source server itself do not affect
the sync status.|See fields below.|
+|`cloneSyncCriteria.maxSegmentsPendingSync`| Experimental. This is a sub-field
inside `cloneSyncCriteria`. For a clone to be considered "synced" with its
source server, the number of segments pending sync must be less than or equal
to this value. |100|
+|`cloneSyncCriteria.maxPercentPendingSync` | Experimental. This is a sub-field
inside `cloneSyncCriteria`. For a clone to be considered "synced" with its
source server, the percentage of segments pending sync must be less than or
equal to this value. |1|
|`historicalTierAliases`|Map from a virtual tier name to the set of real
Historical tier names it expands to. When a load/drop rule references a virtual
alias tier, the Coordinator replaces it with its real tiers — each receiving
the full replica count independently. The alias key itself is never loaded to
directly. For example, `{"hot": ["hot_1", "hot_2"]}` causes a rule of `{"hot":
2}` to load 2 replicas on each of `hot_1` and `hot_2`; `hot` receives no direct
assignment. An alias valu [...]
##### Smart segment loading
diff --git a/docs/operations/metrics.md b/docs/operations/metrics.md
index 8cad874d1a4..62d01316e6a 100644
--- a/docs/operations/metrics.md
+++ b/docs/operations/metrics.md
@@ -452,6 +452,9 @@ These metrics are emitted by the Druid Coordinator in every
run of the correspon
|`segment/assignSkipped/count`|Number of segments that could not be assigned
to any server for loading. This can occur due to replication throttling, no
available disk space, or a full load queue.|`dataSource`, `server`, `tier`,
`description`|Varies|
|`segment/moveSkipped/count`|Number of segments that were chosen for balancing
but could not be moved. This can occur when segments are already optimally
placed.|`dataSource`, `server`, `tier`, `description`|Varies|
|`segment/dropSkipped/count`|Number of segments that could not be dropped from
any server.|`dataSource`, `server`, `tier`, `description`|Varies|
+|`segment/clone/assigned/count`|Number of segments assigned to be loaded on a
historical clone.|`dataSource`, `server`, `tier`|Varies|
+|`segment/clone/dropped/count`|Number of segments dropped from a historical
clone.|`dataSource`, `server`, `tier`|Varies|
+|`segment/clone/pendingSync/count`|Number of segments that still need to be
loaded on a historical clone but are already loaded on its source
server.|`server`, `tier`|Varies|
|`segment/loadQueue/size`|Size in bytes of segments to load.|`server`|Varies|
|`segment/loadQueue/count`|Number of segments to load.|`server`|Varies|
|`segment/loading/rateKbps`|Current rate of segment loading on a server in
kbps (1000 bits per second). The rate is calculated as a moving average over
the last 10 GiB or more of successful segment loads on that
server.|`server`|Varies|
@@ -468,6 +471,8 @@ These metrics are emitted by the Druid Coordinator in every
run of the correspon
|`segment/underReplicated/count`|Number of segments, including replicas, left
to load until all used segments are available for queries.|`tier`,
`dataSource`|0|
|`segment/availableDeepStorageOnly/count`|Number of unique segments that are
only available for querying directly from deep storage.|`dataSource`|Varies|
|`tier/historical/count`|Number of available historical nodes in each tier.
The `tierAlias` dimension is emitted only when the tier belongs to an alias
configured via
[`historicalTierAliases`](../configuration/index.md#dynamic-configuration), and
can be used to aggregate metrics across the tiers in an alias.|`tier`,
`tierAlias`|Varies|
+|`tier/historical/clone/count`|Number of historical nodes in a tier which are
a clone of another historical in the same or different tier. The `tierAlias`
dimension is emitted only when the tier belongs to an alias configured via
[`historicalTierAliases`](../configuration/index.md#dynamic-configuration), and
can be used to aggregate metrics across the tiers in an alias.|`tier`,
`tierAlias`|Varies|
+|`tier/historical/clone/synced`|Number of historical clones in a tier which
are currently in sync with their source server. |`server`, `tier`|1 if synced,
O if not synced|
|`tier/replication/factor`|Configured maximum replication factor in each tier.
The `tierAlias` dimension is emitted only when the tier belongs to an alias
configured via
[`historicalTierAliases`](../configuration/index.md#dynamic-configuration).|`tier`,
`tierAlias`|Varies|
|`tier/required/capacity`|Total capacity in bytes required in each tier. The
`tierAlias` dimension is emitted only when the tier belongs to an alias
configured via
[`historicalTierAliases`](../configuration/index.md#dynamic-configuration).|`tier`,
`tierAlias`|Varies|
|`tier/total/capacity`|Total capacity in bytes available in each tier. The
`tierAlias` dimension is emitted only when the tier belongs to an alias
configured via
[`historicalTierAliases`](../configuration/index.md#dynamic-configuration).|`tier`,
`tierAlias`|Varies|
diff --git
a/processing/src/main/java/org/apache/druid/common/config/Configs.java
b/processing/src/main/java/org/apache/druid/common/config/Configs.java
index 433a2548a4b..d2d10ea804c 100644
--- a/processing/src/main/java/org/apache/druid/common/config/Configs.java
+++ b/processing/src/main/java/org/apache/druid/common/config/Configs.java
@@ -42,6 +42,15 @@ public class Configs
return value == null ? defaultValue : value;
}
+ /**
+ * Returns the given {@code value} if it is not null, otherwise returns the
+ * {@code defaultValue}.
+ */
+ public static double valueOrDefault(Double value, double defaultValue)
+ {
+ return value == null ? defaultValue : value;
+ }
+
/**
* Returns the given {@code value} if it is not null, otherwise returns the
* {@code defaultValue}.
diff --git
a/processing/src/test/java/org/apache/druid/common/config/ConfigsTest.java
b/processing/src/test/java/org/apache/druid/common/config/ConfigsTest.java
index ff4acf7cbea..770aef077da 100644
--- a/processing/src/test/java/org/apache/druid/common/config/ConfigsTest.java
+++ b/processing/src/test/java/org/apache/druid/common/config/ConfigsTest.java
@@ -31,7 +31,10 @@ public class ConfigsTest
Assertions.assertEquals(11, Configs.valueOrDefault((Integer) null, 11));
Assertions.assertEquals(10, Configs.valueOrDefault((Long) 10L, 11L));
- Assertions.assertEquals(11, Configs.valueOrDefault(null, 11L));
+ Assertions.assertEquals(11, Configs.valueOrDefault((Long) null, 11L));
+
+ Assertions.assertEquals(10.0, Configs.valueOrDefault((Double) 10.0, 11.0));
+ Assertions.assertEquals(11.0, Configs.valueOrDefault(null, 11.0));
Assertions.assertFalse(Configs.valueOrDefault((Boolean) false, true));
Assertions.assertTrue(Configs.valueOrDefault(null, true));
diff --git
a/server/src/main/java/org/apache/druid/server/coordinator/CloneSyncCriteria.java
b/server/src/main/java/org/apache/druid/server/coordinator/CloneSyncCriteria.java
new file mode 100644
index 00000000000..682378c3f31
--- /dev/null
+++
b/server/src/main/java/org/apache/druid/server/coordinator/CloneSyncCriteria.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.server.coordinator;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.druid.common.config.Configs;
+import org.apache.druid.error.InvalidInput;
+
+import javax.annotation.Nullable;
+
+/**
+ * Criteria defining when a clone historical should be considered as
+ * {@link ServerCloneStatus.State#SYNCED} to its source server. The criteria is
+ * a function of the number and percentage of segments "pending sync", i.e.
+ * segments already loaded on the source server but still loading on the target
+ * server. Segments which are yet to be loaded on the source server itself do
not
+ * affect the sync status.
+ */
+public record CloneSyncCriteria(
+ @JsonProperty("maxSegmentsPendingSync") @Nullable Integer
maxSegmentsPendingSync,
+ @JsonProperty("maxPercentPendingSync") @Nullable Double
maxPercentPendingSync
+)
+{
+ public static final int DEFAULT_MAX_SEGMENTS_PENDING_SYNC = 100;
+ public static final double DEFAULT_MAX_PERCENT_PENDING_SYNC = 1.0;
+
+ public CloneSyncCriteria
+ {
+ InvalidInput.conditionalException(
+ maxSegmentsPendingSync == null || maxSegmentsPendingSync >= 0,
+ "'maxSegmentsPendingSync' must be greater than or equal to 0"
+ );
+ InvalidInput.conditionalException(
+ maxPercentPendingSync == null || (maxPercentPendingSync >= 0.0 &&
maxPercentPendingSync <= 100.0),
+ "'maxPercentPendingSync' must be in the range [0.0, 100.0]"
+ );
+ }
+
+ /**
+ * For a clone to be considered SYNCED, the number of segments pending sync
+ * must be less than or equal to this value.
+ * Default value is {@link #DEFAULT_MAX_SEGMENTS_PENDING_SYNC}.
+ */
+ public int getMaxSegmentsPendingSync()
+ {
+ return Configs.valueOrDefault(maxSegmentsPendingSync,
DEFAULT_MAX_SEGMENTS_PENDING_SYNC);
+ }
+
+ /**
+ * For a clone to be considered SYNCED, the percentage of segments pending
sync
+ * must be less than or equal to this value.
+ * Default value is {@link #DEFAULT_MAX_PERCENT_PENDING_SYNC}.
+ */
+ public double getMaxPercentPendingSync()
+ {
+ return Configs.valueOrDefault(maxPercentPendingSync,
DEFAULT_MAX_PERCENT_PENDING_SYNC);
+ }
+}
diff --git
a/server/src/main/java/org/apache/druid/server/coordinator/CoordinatorDynamicConfig.java
b/server/src/main/java/org/apache/druid/server/coordinator/CoordinatorDynamicConfig.java
index e0ea80e2446..554a8219294 100644
---
a/server/src/main/java/org/apache/druid/server/coordinator/CoordinatorDynamicConfig.java
+++
b/server/src/main/java/org/apache/druid/server/coordinator/CoordinatorDynamicConfig.java
@@ -77,6 +77,7 @@ public class CoordinatorDynamicConfig
private final Set<String> turboLoadingNodes;
private final Map<String, String> cloneServers;
+ private final CloneSyncCriteria cloneSyncCriteria;
/**
* Map from alias tier name to the set of actual tier names it represents.
@@ -143,6 +144,7 @@ public class CoordinatorDynamicConfig
@JsonProperty("debugDimensions") @Nullable Map<String, String>
debugDimensions,
@JsonProperty("turboLoadingNodes") @Nullable Set<String>
turboLoadingNodes,
@JsonProperty("cloneServers") @Nullable Map<String, String> cloneServers,
+ @JsonProperty("cloneSyncCriteria") @Nullable CloneSyncCriteria
cloneSyncCriteria,
@JsonProperty("historicalTierAliases") @Nullable Map<String,
Set<String>> historicalTierAliases
)
{
@@ -189,6 +191,7 @@ public class CoordinatorDynamicConfig
this.validDebugDimensions = validateDebugDimensions(debugDimensions);
this.turboLoadingNodes = Configs.valueOrDefault(turboLoadingNodes,
Set.of());
this.cloneServers = Configs.valueOrDefault(cloneServers, Map.of());
+ this.cloneSyncCriteria = Configs.valueOrDefault(cloneSyncCriteria, new
CloneSyncCriteria(null, null));
this.historicalTierAliases = Configs.valueOrDefault(historicalTierAliases,
Map.of());
final Set<String> aliasKeys = this.historicalTierAliases.keySet();
@@ -392,6 +395,18 @@ public class CoordinatorDynamicConfig
return cloneServers;
}
+ /**
+ * Criteria defining when a target server specified in {@link #cloneServers}
+ * can be considered as {@link ServerCloneStatus.State#SYNCED}. A clone once
+ * marked as synced may again go out of sync in subsequent coordinator cycles
+ * if any of the conditions or sync criteria change.
+ */
+ @JsonProperty
+ public CloneSyncCriteria getCloneSyncCriteria()
+ {
+ return cloneSyncCriteria;
+ }
+
@JsonProperty
public Map<String, Set<String>> getHistoricalTierAliases()
{
@@ -442,6 +457,7 @@ public class CoordinatorDynamicConfig
", replicateAfterLoadTimeout=" + replicateAfterLoadTimeout +
", turboLoadingNodes=" + turboLoadingNodes +
", cloneServers=" + cloneServers +
+ ", cloneSyncCriteria=" + cloneSyncCriteria +
", historicalTierAliases=" + historicalTierAliases +
'}';
}
@@ -480,6 +496,7 @@ public class CoordinatorDynamicConfig
&& Objects.equals(turboLoadingNodes, that.turboLoadingNodes)
&& Objects.equals(debugDimensions, that.debugDimensions)
&& Objects.equals(cloneServers, that.cloneServers)
+ && Objects.equals(cloneSyncCriteria, that.cloneSyncCriteria)
&& Objects.equals(historicalTierAliases,
that.historicalTierAliases);
}
@@ -505,6 +522,7 @@ public class CoordinatorDynamicConfig
debugDimensions,
turboLoadingNodes,
cloneServers,
+ cloneSyncCriteria,
historicalTierAliases
);
}
@@ -562,6 +580,7 @@ public class CoordinatorDynamicConfig
private Boolean smartSegmentLoading;
private Set<String> turboLoadingNodes;
private Map<String, String> cloneServers;
+ private CloneSyncCriteria cloneSyncCriteria;
private Map<String, Set<String>> historicalTierAliases;
public Builder()
@@ -588,6 +607,7 @@ public class CoordinatorDynamicConfig
@JsonProperty("debugDimensions") @Nullable Map<String, String>
debugDimensions,
@JsonProperty("turboLoadingNodes") @Nullable Set<String>
turboLoadingNodes,
@JsonProperty("cloneServers") @Nullable Map<String, String>
cloneServers,
+ @JsonProperty("cloneSyncCriteria") @Nullable CloneSyncCriteria
cloneSyncCriteria,
@JsonProperty("historicalTierAliases") @Nullable Map<String,
Set<String>> historicalTierAliases
)
{
@@ -609,6 +629,7 @@ public class CoordinatorDynamicConfig
this.debugDimensions = debugDimensions;
this.turboLoadingNodes = turboLoadingNodes;
this.cloneServers = cloneServers;
+ this.cloneSyncCriteria = cloneSyncCriteria;
this.historicalTierAliases = historicalTierAliases;
}
@@ -708,6 +729,12 @@ public class CoordinatorDynamicConfig
return this;
}
+ public Builder withCloneSyncCriteria(CloneSyncCriteria cloneSyncCriteria)
+ {
+ this.cloneSyncCriteria = cloneSyncCriteria;
+ return this;
+ }
+
public Builder withHistoricalTierAliases(Map<String, Set<String>>
historicalTierAliases)
{
this.historicalTierAliases = historicalTierAliases;
@@ -742,6 +769,7 @@ public class CoordinatorDynamicConfig
debugDimensions,
turboLoadingNodes,
cloneServers,
+ cloneSyncCriteria,
historicalTierAliases
);
}
@@ -775,6 +803,7 @@ public class CoordinatorDynamicConfig
valueOrDefault(debugDimensions, defaults.getDebugDimensions()),
valueOrDefault(turboLoadingNodes, defaults.getTurboLoadingNodes()),
valueOrDefault(cloneServers, defaults.getCloneServers()),
+ valueOrDefault(cloneSyncCriteria, defaults.getCloneSyncCriteria()),
valueOrDefault(historicalTierAliases,
defaults.getHistoricalTierAliases())
);
}
diff --git
a/server/src/main/java/org/apache/druid/server/coordinator/ServerCloneStatus.java
b/server/src/main/java/org/apache/druid/server/coordinator/ServerCloneStatus.java
index 0d011fd1243..56e319438be 100644
---
a/server/src/main/java/org/apache/druid/server/coordinator/ServerCloneStatus.java
+++
b/server/src/main/java/org/apache/druid/server/coordinator/ServerCloneStatus.java
@@ -19,127 +19,31 @@
package org.apache.druid.server.coordinator;
-import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
-import java.util.Objects;
-
/**
* Immutable class which represents the current status of a single clone
server.
+ *
+ * @param segmentsPendingSync Number of segments that are already loaded on the
+ * source server but are yet to be loaded on the
target server.
*/
-public class ServerCloneStatus
+public record ServerCloneStatus(
+ @JsonProperty("sourceServer") String sourceServer,
+ @JsonProperty("targetServer") String targetServer,
+ @JsonProperty("state") State state,
+ @JsonProperty("segmentLoadsRemaining") long segmentLoadsRemaining,
+ @JsonProperty("segmentDropsRemaining") long segmentDropsRemaining,
+ @JsonProperty("segmentsPendingSync") long segmentsPendingSync,
+ @JsonProperty("percentPendingSync") double percentPendingSync,
+ @JsonProperty("bytesToLoad") long bytesToLoad
+)
{
- private final String sourceServer;
- private final String targetServer;
- private final State state;
- private final long segmentLoadsRemaining;
- private final long segmentDropsRemaining;
- private final long bytesToLoad;
-
- @JsonCreator
- public ServerCloneStatus(
- @JsonProperty("sourceServer") String sourceServer,
- @JsonProperty("targetServer") String targetServer,
- @JsonProperty("state") State state,
- @JsonProperty("segmentLoadsRemaining") long segmentLoadsRemaining,
- @JsonProperty("segmentDropsRemaining") long segmentDropsRemaining,
- @JsonProperty("bytesToLoad") long bytesToLoad
- )
- {
- this.sourceServer = sourceServer;
- this.targetServer = targetServer;
- this.state = state;
- this.segmentLoadsRemaining = segmentLoadsRemaining;
- this.segmentDropsRemaining = segmentDropsRemaining;
- this.bytesToLoad = bytesToLoad;
- }
-
- @JsonProperty
- public String getSourceServer()
- {
- return sourceServer;
- }
-
- @JsonProperty
- public String getTargetServer()
- {
- return targetServer;
- }
-
- @JsonProperty
- public long getSegmentLoadsRemaining()
- {
- return segmentLoadsRemaining;
- }
-
- @JsonProperty
- public long getSegmentDropsRemaining()
- {
- return segmentDropsRemaining;
- }
-
- @JsonProperty
- public long getBytesToLoad()
- {
- return bytesToLoad;
- }
-
- @JsonProperty
- public State getState()
- {
- return state;
- }
-
/**
* Create a {@link ServerCloneStatus} where the current status is unknown as
the target server is missing.
*/
public static ServerCloneStatus unknown(String sourceServer, String
targetServer)
{
- return new ServerCloneStatus(sourceServer, targetServer,
State.TARGET_SERVER_MISSING, -1, -1, -1);
- }
-
- @Override
- public boolean equals(Object o)
- {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- ServerCloneStatus that = (ServerCloneStatus) o;
- return segmentLoadsRemaining == that.segmentLoadsRemaining
- && segmentDropsRemaining == that.segmentDropsRemaining
- && bytesToLoad == that.bytesToLoad
- && Objects.equals(sourceServer, that.sourceServer)
- && Objects.equals(targetServer, that.targetServer)
- && state == that.state;
- }
-
- @Override
- public int hashCode()
- {
- return Objects.hash(
- sourceServer,
- targetServer,
- state,
- segmentLoadsRemaining,
- segmentDropsRemaining,
- bytesToLoad
- );
- }
-
- @Override
- public String toString()
- {
- return "ServerCloneStatus{" +
- "sourceServer='" + sourceServer + '\'' +
- ", targetServer='" + targetServer + '\'' +
- ", state=" + state +
- ", segmentLoadsRemaining=" + segmentLoadsRemaining +
- ", segmentDropsRemaining=" + segmentDropsRemaining +
- ", bytesToLoad=" + bytesToLoad +
- '}';
+ return new ServerCloneStatus(sourceServer, targetServer,
State.TARGET_SERVER_MISSING, -1, -1, -1, -1, -1);
}
/**
@@ -159,6 +63,14 @@ public class ServerCloneStatus
/**
* Segments are loaded or being loaded. The counts give a better view of
the progress.
*/
- IN_PROGRESS
+ IN_PROGRESS,
+ /**
+ * Clone server has caught up with the source server. This is not a
permanent
+ * state and if sync criteria are updated or if the clone starts lagging
behind,
+ * subsequent coordinator cycles may move the clone back to {@link
#IN_PROGRESS}.
+ *
+ * @see CloneSyncCriteria
+ */
+ SYNCED
}
}
diff --git
a/server/src/main/java/org/apache/druid/server/coordinator/duty/CloneHistoricals.java
b/server/src/main/java/org/apache/druid/server/coordinator/duty/CloneHistoricals.java
index 82441810634..5112f61a3c1 100644
---
a/server/src/main/java/org/apache/druid/server/coordinator/duty/CloneHistoricals.java
+++
b/server/src/main/java/org/apache/druid/server/coordinator/duty/CloneHistoricals.java
@@ -22,6 +22,7 @@ package org.apache.druid.server.coordinator.duty;
import org.apache.druid.client.ImmutableDruidDataSource;
import org.apache.druid.java.util.common.logger.Logger;
import org.apache.druid.server.coordinator.CloneStatusManager;
+import org.apache.druid.server.coordinator.CloneSyncCriteria;
import org.apache.druid.server.coordinator.CoordinatorDynamicConfig;
import org.apache.druid.server.coordinator.DruidCluster;
import org.apache.druid.server.coordinator.DruidCoordinatorRuntimeParams;
@@ -30,6 +31,7 @@ import org.apache.druid.server.coordinator.ServerHolder;
import org.apache.druid.server.coordinator.loading.PartialLoadProfile;
import org.apache.druid.server.coordinator.loading.SegmentAction;
import org.apache.druid.server.coordinator.loading.SegmentLoadQueueManager;
+import org.apache.druid.server.coordinator.stats.CoordinatorRunStats;
import org.apache.druid.server.coordinator.stats.Dimension;
import org.apache.druid.server.coordinator.stats.RowKey;
import org.apache.druid.server.coordinator.stats.Stats;
@@ -94,6 +96,7 @@ public class CloneHistoricals implements CoordinatorDuty
serverHolder -> serverHolder
));
+ final Map<String, CloningStats> targetHistoricalStats = new HashMap<>();
for (Map.Entry<String, String> entry : cloneServers.entrySet()) {
final String targetHistoricalName = entry.getKey();
final ServerHolder targetServer =
hostToHistoricalMap.get(targetHistoricalName);
@@ -110,6 +113,9 @@ public class CloneHistoricals implements CoordinatorDuty
continue;
}
+ final CloningStats cloningStats = new
CloningStats(sourceServer.getServer().getNumSegments());
+ targetHistoricalStats.put(targetHistoricalName, cloningStats);
+
final Set<DataSegment> sourceProjectedSegments =
sourceServer.getProjectedSegments();
final Set<DataSegment> targetProjectedSegments =
targetServer.getProjectedSegments();
// Load any segment that the clone target is missing, or that it holds
under a different partial-load profile
@@ -120,6 +126,12 @@ public class CloneHistoricals implements CoordinatorDuty
if (shouldLoadSegmentOnTargetServer(segment, sourceProfile,
targetServer, targetProjectedSegments)) {
loadSegmentOnTargetServer(segment, sourceProfile, targetServer,
params);
}
+
+ if (params.isUsedSegment(segment)
+ && targetServer.isLoadingSegment(segment)
+ && sourceServer.isServingSegment(segment)) {
+
cloningStats.incrementMissingSegmentCount(sourceServer.isServingSegment(segment));
+ }
}
// Drop any segments missing from the clone source.
@@ -130,7 +142,12 @@ public class CloneHistoricals implements CoordinatorDuty
}
}
- final Map<String, ServerCloneStatus> newStatusMap =
createCurrentStatusMap(hostToHistoricalMap, cloneServers);
+ final Map<String, ServerCloneStatus> newStatusMap = createCurrentStatusMap(
+ hostToHistoricalMap,
+ cloneServers,
+ targetHistoricalStats,
+ params
+ );
cloneStatusManager.updateStatus(newStatusMap);
return params;
@@ -149,6 +166,7 @@ public class CloneHistoricals implements CoordinatorDuty
{
final RowKey.Builder rowKey = RowKey
.with(Dimension.SERVER, targetServer.getServer().getName())
+ .with(Dimension.TIER, targetServer.getServer().getTier())
.with(Dimension.DATASOURCE, segment.getDataSource());
final DataSegment loadableSegment = getLoadableSegment(segment, params);
@@ -187,9 +205,13 @@ public class CloneHistoricals implements CoordinatorDuty
if (targetServer.isLoadingSegment(segment)) {
targetServer.cancelLoad(segment);
} else if (loadQueueManager.dropSegment(segment, targetServer)) {
+ final RowKey rowKey = RowKey
+ .with(Dimension.SERVER, targetServer.getServer().getName())
+ .with(Dimension.TIER, targetServer.getServer().getTier())
+ .and(Dimension.DATASOURCE, segment.getDataSource());
params.getCoordinatorStats().add(
Stats.Segments.DROPPED_FROM_CLONE,
- RowKey.of(Dimension.SERVER, targetServer.getServer().getName()),
+ rowKey,
1L
);
}
@@ -221,10 +243,13 @@ public class CloneHistoricals implements CoordinatorDuty
*/
private Map<String, ServerCloneStatus> createCurrentStatusMap(
Map<String, ServerHolder> historicalMap,
- Map<String, String> cloneServers
+ Map<String, String> cloneServers,
+ Map<String, CloningStats> targetHistoricalStats,
+ DruidCoordinatorRuntimeParams params
)
{
final Map<String, ServerCloneStatus> newStatusMap = new HashMap<>();
+ final CloneSyncCriteria cloneSyncCriteria =
params.getCoordinatorDynamicConfig().getCloneSyncCriteria();
for (Map.Entry<String, String> entry : cloneServers.entrySet()) {
final String targetServerName = entry.getKey();
@@ -239,10 +264,12 @@ public class CloneHistoricals implements CoordinatorDuty
if (targetServer == null) {
newStatus = ServerCloneStatus.unknown(sourceServerName,
targetServerName);
} else {
-
ServerCloneStatus.State state;
+ final CloningStats stats =
targetHistoricalStats.getOrDefault(targetServerName, new CloningStats(0));
if (!historicalMap.containsKey(sourceServerName)) {
state = ServerCloneStatus.State.SOURCE_SERVER_MISSING;
+ } else if (isSynced(stats, cloneSyncCriteria)) {
+ state = ServerCloneStatus.State.SYNCED;
} else {
state = ServerCloneStatus.State.IN_PROGRESS;
}
@@ -255,7 +282,17 @@ public class CloneHistoricals implements CoordinatorDuty
segmentDrop += 1;
}
}
- newStatus = new ServerCloneStatus(sourceServerName, targetServerName,
state, segmentLoad, segmentDrop, bytesLeft);
+ newStatus = new ServerCloneStatus(
+ sourceServerName,
+ targetServerName,
+ state,
+ segmentLoad,
+ segmentDrop,
+ stats.segmentsPendingSync,
+ stats.percentPendingSync(),
+ bytesLeft
+ );
+ collectMetrics(targetServer, newStatus, params.getCoordinatorStats());
}
newStatusMap.put(targetServerName, newStatus);
}
@@ -285,4 +322,62 @@ public class CloneHistoricals implements CoordinatorDuty
final PartialLoadProfile targetProfile =
targetServer.getProjectedProfile(segment);
return !Objects.equals(fingerprintOf(sourceProfile),
fingerprintOf(targetProfile));
}
+
+ private boolean isSynced(CloningStats stats, CloneSyncCriteria criteria)
+ {
+ return stats.segmentsPendingSync <= criteria.getMaxSegmentsPendingSync()
+ && stats.percentPendingSync() <= criteria.getMaxPercentPendingSync();
+ }
+
+ /**
+ * Adds metrics for this clone to the run stats.
+ */
+ private void collectMetrics(
+ ServerHolder targetServer,
+ ServerCloneStatus newStatus,
+ CoordinatorRunStats stats
+ )
+ {
+ final String targetServerName = targetServer.getServer().getName();
+ final RowKey rowKey = RowKey.with(Dimension.SERVER, targetServerName)
+ .and(Dimension.TIER,
targetServer.getServer().getTier());
+ stats.add(Stats.Segments.PENDING_SYNC_ON_CLONE, rowKey,
newStatus.segmentsPendingSync());
+
+ final boolean isSynced = newStatus.state() ==
ServerCloneStatus.State.SYNCED;
+ stats.add(Stats.Tier.CLONE_SYNCED, rowKey, isSynced ? 1L : 0L);
+ }
+
+ private static class CloningStats
+ {
+ final int totalSegmentsLoadedOnSource;
+
+ /**
+ * Number of segments already loaded on source server but not on target
server.
+ */
+ int segmentsPendingSync = 0;
+
+ CloningStats(int totalSegmentsLoadedOnSource)
+ {
+ this.totalSegmentsLoadedOnSource = totalSegmentsLoadedOnSource;
+ }
+
+ void incrementMissingSegmentCount(boolean loadedOnSourceServer)
+ {
+ if (loadedOnSourceServer) {
+ ++segmentsPendingSync;
+ }
+ }
+
+ /**
+ * Percentage of segments pending sync.
+ */
+ double percentPendingSync()
+ {
+ if (totalSegmentsLoadedOnSource > 0) {
+ return (100.0 * segmentsPendingSync) / totalSegmentsLoadedOnSource;
+ } else {
+ return 0.0;
+ }
+ }
+ }
}
diff --git
a/server/src/main/java/org/apache/druid/server/coordinator/stats/Stats.java
b/server/src/main/java/org/apache/druid/server/coordinator/stats/Stats.java
index 80368ba7558..138d7068189 100644
--- a/server/src/main/java/org/apache/druid/server/coordinator/stats/Stats.java
+++ b/server/src/main/java/org/apache/druid/server/coordinator/stats/Stats.java
@@ -71,6 +71,8 @@ public class Stats
= CoordinatorStat.toDebugAndEmit("cloneLoad",
"segment/clone/assigned/count");
public static final CoordinatorStat DROPPED_FROM_CLONE
= CoordinatorStat.toDebugAndEmit("cloneDrop",
"segment/clone/dropped/count");
+ public static final CoordinatorStat PENDING_SYNC_ON_CLONE
+ = CoordinatorStat.toDebugAndEmit("clonePendingSync",
"segment/clone/pendingSync/count");
// Partial-load reconciliation in a run
public static final CoordinatorStat PARTIAL_ASSIGNED
@@ -118,6 +120,8 @@ public class Stats
= CoordinatorStat.toDebugAndEmit("numHistorical",
"tier/historical/count");
public static final CoordinatorStat CLONE_COUNT
= CoordinatorStat.toDebugAndEmit("numClones",
"tier/historical/clone/count");
+ public static final CoordinatorStat CLONE_SYNCED
+ = CoordinatorStat.toDebugAndEmit("cloneSynced",
"tier/historical/clone/synced");
}
public static class Compaction
diff --git
a/server/src/test/java/org/apache/druid/server/http/CoordinatorDynamicConfigTest.java
b/server/src/test/java/org/apache/druid/server/coordinator/CoordinatorDynamicConfigTest.java
similarity index 99%
rename from
server/src/test/java/org/apache/druid/server/http/CoordinatorDynamicConfigTest.java
rename to
server/src/test/java/org/apache/druid/server/coordinator/CoordinatorDynamicConfigTest.java
index 95b436fc745..421ca427a36 100644
---
a/server/src/test/java/org/apache/druid/server/http/CoordinatorDynamicConfigTest.java
+++
b/server/src/test/java/org/apache/druid/server/coordinator/CoordinatorDynamicConfigTest.java
@@ -17,7 +17,7 @@
* under the License.
*/
-package org.apache.druid.server.http;
+package org.apache.druid.server.coordinator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
@@ -25,7 +25,7 @@ import com.google.common.collect.ImmutableSet;
import nl.jqno.equalsverifier.EqualsVerifier;
import org.apache.druid.error.DruidException;
import org.apache.druid.segment.TestHelper;
-import org.apache.druid.server.coordinator.CoordinatorDynamicConfig;
+import org.apache.druid.server.http.SegmentLoadingMode;
import org.apache.druid.utils.JvmUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -281,6 +281,7 @@ public class CoordinatorDynamicConfigTest
null,
ImmutableSet.of("host1"),
null,
+ null,
null
);
Assertions.assertTrue(config.getSpecificDataSourcesToKillUnusedSegmentsIn().isEmpty());
@@ -308,6 +309,7 @@ public class CoordinatorDynamicConfigTest
null,
ImmutableSet.of("host1"),
null,
+ null,
null
);
Assertions.assertEquals(ImmutableSet.of("test1"),
config.getSpecificDataSourcesToKillUnusedSegmentsIn());
diff --git
a/server/src/test/java/org/apache/druid/server/coordinator/ServerCloneStatusTest.java
b/server/src/test/java/org/apache/druid/server/coordinator/ServerCloneStatusTest.java
index e0aa95e493f..6172d9b9c9e 100644
---
a/server/src/test/java/org/apache/druid/server/coordinator/ServerCloneStatusTest.java
+++
b/server/src/test/java/org/apache/druid/server/coordinator/ServerCloneStatusTest.java
@@ -29,7 +29,16 @@ public class ServerCloneStatusTest
@Test
public void testSerde() throws Exception
{
- ServerCloneStatus metrics = new ServerCloneStatus("host2", "host1",
ServerCloneStatus.State.IN_PROGRESS, 3012, 10, 100);
+ ServerCloneStatus metrics = new ServerCloneStatus(
+ "host2",
+ "host1",
+ ServerCloneStatus.State.IN_PROGRESS,
+ 3012,
+ 10,
+ 1,
+ 1,
+ 100
+ );
byte[] bytes = DefaultObjectMapper.INSTANCE.writeValueAsBytes(metrics);
ServerCloneStatus deserialized =
DefaultObjectMapper.INSTANCE.readValue(bytes, ServerCloneStatus.class);
Assertions.assertEquals(deserialized, metrics);
diff --git
a/server/src/test/java/org/apache/druid/server/coordinator/simulate/CoordinatorSimulation.java
b/server/src/test/java/org/apache/druid/server/coordinator/simulate/CoordinatorSimulation.java
index 8a6ff4b8205..b1b6a618ff4 100644
---
a/server/src/test/java/org/apache/druid/server/coordinator/simulate/CoordinatorSimulation.java
+++
b/server/src/test/java/org/apache/druid/server/coordinator/simulate/CoordinatorSimulation.java
@@ -22,10 +22,12 @@ package org.apache.druid.server.coordinator.simulate;
import org.apache.druid.client.DruidServer;
import org.apache.druid.java.util.metrics.MetricsVerifier;
import org.apache.druid.server.coordinator.CoordinatorDynamicConfig;
+import org.apache.druid.server.coordinator.ServerCloneStatus;
import org.apache.druid.server.coordinator.loading.SegmentHolder;
import org.apache.druid.server.coordinator.rules.Rule;
import org.apache.druid.timeline.DataSegment;
+import javax.annotation.Nullable;
import java.util.List;
/**
@@ -97,6 +99,12 @@ public interface CoordinatorSimulation
* Gets the load percentage of the specified datasource as seen by the
coordinator.
*/
double getLoadPercentage(String datasource);
+
+ /**
+ * Gets the current cloning status for the given clone target server.
+ */
+ @Nullable
+ ServerCloneStatus getCloneStatus(DruidServer cloneTarget);
}
interface ClusterState
diff --git
a/server/src/test/java/org/apache/druid/server/coordinator/simulate/CoordinatorSimulationBaseTest.java
b/server/src/test/java/org/apache/druid/server/coordinator/simulate/CoordinatorSimulationBaseTest.java
index 9e8e16c8f25..59d4bb73321 100644
---
a/server/src/test/java/org/apache/druid/server/coordinator/simulate/CoordinatorSimulationBaseTest.java
+++
b/server/src/test/java/org/apache/druid/server/coordinator/simulate/CoordinatorSimulationBaseTest.java
@@ -26,6 +26,7 @@ import org.apache.druid.segment.TestDataSource;
import org.apache.druid.server.coordination.ServerType;
import org.apache.druid.server.coordinator.CoordinatorDynamicConfig;
import org.apache.druid.server.coordinator.CreateDataSegments;
+import org.apache.druid.server.coordinator.ServerCloneStatus;
import org.apache.druid.server.coordinator.loading.SegmentHolder;
import
org.apache.druid.server.coordinator.rules.ForeverBroadcastDistributionRule;
import org.apache.druid.server.coordinator.rules.ForeverDropRule;
@@ -37,6 +38,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
+import javax.annotation.Nullable;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -169,6 +171,13 @@ public abstract class CoordinatorSimulationBaseTest
implements
return sim.coordinator().getLoadPercentage(datasource);
}
+ @Nullable
+ @Override
+ public ServerCloneStatus getCloneStatus(DruidServer cloneTarget)
+ {
+ return sim.coordinator().getCloneStatus(cloneTarget);
+ }
+
// Verification methods
void verifyDatasourceIsFullyLoaded(String datasource)
{
diff --git
a/server/src/test/java/org/apache/druid/server/coordinator/simulate/CoordinatorSimulationBuilder.java
b/server/src/test/java/org/apache/druid/server/coordinator/simulate/CoordinatorSimulationBuilder.java
index 3484b7782d9..5088d450cbd 100644
---
a/server/src/test/java/org/apache/druid/server/coordinator/simulate/CoordinatorSimulationBuilder.java
+++
b/server/src/test/java/org/apache/druid/server/coordinator/simulate/CoordinatorSimulationBuilder.java
@@ -48,6 +48,7 @@ import
org.apache.druid.server.coordinator.CoordinatorDynamicConfig;
import org.apache.druid.server.coordinator.DruidCompactionConfig;
import org.apache.druid.server.coordinator.DruidCoordinator;
import org.apache.druid.server.coordinator.MetadataManager;
+import org.apache.druid.server.coordinator.ServerCloneStatus;
import org.apache.druid.server.coordinator.balancer.BalancerStrategyFactory;
import
org.apache.druid.server.coordinator.balancer.CachingCostBalancerStrategyConfig;
import
org.apache.druid.server.coordinator.balancer.CachingCostBalancerStrategyFactory;
@@ -73,6 +74,7 @@ import org.apache.druid.timeline.SegmentId;
import org.easymock.EasyMock;
import org.joda.time.Duration;
+import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -416,6 +418,13 @@ public class CoordinatorSimulationBuilder
return coordinator.getDatasourceToLoadStatus().get(datasource);
}
+ @Nullable
+ @Override
+ public ServerCloneStatus getCloneStatus(DruidServer cloneTarget)
+ {
+ return env.cloneStatusManager.getStatusForServer(cloneTarget.getName());
+ }
+
@Override
public MetricsVerifier getMetricsVerifier()
{
diff --git
a/server/src/test/java/org/apache/druid/server/coordinator/simulate/HistoricalCloningTest.java
b/server/src/test/java/org/apache/druid/server/coordinator/simulate/HistoricalCloningTest.java
index 1eedb33363d..9ee48912677 100644
---
a/server/src/test/java/org/apache/druid/server/coordinator/simulate/HistoricalCloningTest.java
+++
b/server/src/test/java/org/apache/druid/server/coordinator/simulate/HistoricalCloningTest.java
@@ -21,12 +21,16 @@ package org.apache.druid.server.coordinator.simulate;
import org.apache.druid.client.DruidServer;
import org.apache.druid.segment.TestDataSource;
+import org.apache.druid.server.coordinator.CloneSyncCriteria;
import org.apache.druid.server.coordinator.CoordinatorDynamicConfig;
+import org.apache.druid.server.coordinator.ServerCloneStatus;
import org.apache.druid.server.coordinator.stats.Stats;
+import org.apache.druid.timeline.DataSegment;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import java.util.List;
import java.util.Map;
public class HistoricalCloningTest extends CoordinatorSimulationBaseTest
@@ -344,4 +348,61 @@ public class HistoricalCloningTest extends
CoordinatorSimulationBaseTest
Assertions.assertEquals(5, historicalT11.getTotalSegments());
Assertions.assertEquals(5, historicalT12.getTotalSegments());
}
+
+ @Test
+ public void test_cloneIsSynced_ifLoadedSegmentsMeetCriteria()
+ {
+ final List<DataSegment> segments = Segments.WIKI_10X1D;
+ final CoordinatorSimulation sim =
+ CoordinatorSimulation.builder()
+ .withSegments(segments)
+ .withServers(historicalT11, historicalT12)
+ .withRules(datasource, Load.on(Tier.T1,
1).forever())
+ .withDynamicConfig(
+ CoordinatorDynamicConfig
+ .builder()
+
.withCloneServers(Map.of(historicalT12.getHost(), historicalT11.getHost()))
+ .withCloneSyncCriteria(new
CloneSyncCriteria(1, 15.0))
+ .withSmartSegmentLoading(true)
+ .build()
+ )
+ .build();
+
+
+ // Load 9 segments on histT11
+ segments.forEach(historicalT11::addDataSegment);
+ historicalT11.removeDataSegment(segments.getFirst().getId());
+
+ // Load 8 segments on histT12
+ segments.forEach(historicalT12::addDataSegment);
+ historicalT12.removeDataSegment(segments.getFirst().getId());
+ historicalT12.removeDataSegment(segments.getLast().getId());
+
+ startSimulation(sim);
+ runCoordinatorCycle();
+
+ // Verify that 2 segments are assigned to the clone but only 1 is required
for full sync
+ verifyValue(
+ Stats.Segments.ASSIGNED_TO_CLONE.getMetricName(),
+ Map.of("server", historicalT12.getName()),
+ 2L
+ );
+ verifyValue(
+ Stats.Segments.PENDING_SYNC_ON_CLONE.getMetricName(),
+ Map.of("server", historicalT12.getName()),
+ 1L
+ );
+ verifyValue(
+ Stats.Tier.CLONE_SYNCED.getMetricName(),
+ Map.of("server", historicalT12.getName(), "tier", Tier.T1),
+ 1L
+ );
+
+ // Verify that the clone is already considered as synced since criteria is
met
+ final ServerCloneStatus cloneStatus = getCloneStatus(historicalT12);
+ Assertions.assertNotNull(cloneStatus);
+ Assertions.assertEquals(2, cloneStatus.segmentLoadsRemaining());
+ Assertions.assertEquals(1, cloneStatus.segmentsPendingSync());
+ Assertions.assertEquals(ServerCloneStatus.State.SYNCED,
cloneStatus.state());
+ }
}
diff --git
a/server/src/test/java/org/apache/druid/server/http/CoordinatorDynamicConfigsResourceTest.java
b/server/src/test/java/org/apache/druid/server/http/CoordinatorDynamicConfigsResourceTest.java
index b1ba0f1b36e..d2ca54b74ef 100644
---
a/server/src/test/java/org/apache/druid/server/http/CoordinatorDynamicConfigsResourceTest.java
+++
b/server/src/test/java/org/apache/druid/server/http/CoordinatorDynamicConfigsResourceTest.java
@@ -84,7 +84,7 @@ public class CoordinatorDynamicConfigsResourceTest
public void testGetCloneStatus()
{
List<ServerCloneStatus> statusMetrics = ImmutableList.of(
- new ServerCloneStatus("hist3", "hist1",
ServerCloneStatus.State.IN_PROGRESS, 2, 0, 1000),
+ new ServerCloneStatus("hist3", "hist1",
ServerCloneStatus.State.IN_PROGRESS, 2, 0, 1, 1, 1000),
ServerCloneStatus.unknown("hist4", "hist3")
);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]