Skip to content
Closed
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
447de70
Add assigned queued and limit INIT per node
ywangd Jul 19, 2025
a74f94b
renames and tweak
ywangd Jul 20, 2025
f76b13a
Fix deletions
ywangd Jul 20, 2025
55a6907
Extract common method
ywangd Jul 20, 2025
8eb71ef
Fix allQueued snapshot test for deletion
ywangd Jul 20, 2025
82765b2
Change back to assigned queued
ywangd Jul 21, 2025
4292017
improve node counting a bit
ywangd Jul 21, 2025
12dfdc3
Allow relocation and add an IT
ywangd Jul 21, 2025
cd574b8
Update docs/changelog/131592.yaml
ywangd Jul 21, 2025
3a76132
rename
ywangd Jul 21, 2025
49e1b42
Fix a bug where AssignedQueued shard fails with MISSING
ywangd Jul 22, 2025
6c9fdaa
Merge remote-tracking branch 'origin/main' into shard-snapshot-limit-…
ywangd Jul 22, 2025
5ecc2cf
Fix bug where assigned-queued can be left forever
ywangd Jul 23, 2025
ae42b23
comments
ywangd Jul 23, 2025
fcfbc63
actually use updated states
ywangd Jul 23, 2025
329a2c5
TODO about removing the kickoff logic after deletion removal
ywangd Jul 24, 2025
f60009a
Merge remote-tracking branch 'origin/main' into shard-snapshot-limit-…
ywangd Jul 24, 2025
8396c67
Merge remote-tracking branch 'origin/main' into shard-snapshot-limit-…
ywangd Jul 25, 2025
c6d86c9
Revert "TODO about removing the kickoff logic after deletion removal"
ywangd Jul 27, 2025
726da41
remove kickoff process after deletion removal
ywangd Jul 25, 2025
0fde650
Fix a bug where assigned-queued shards are not kicked off
ywangd Jul 27, 2025
c76f244
One more TODO
ywangd Jul 27, 2025
d6d0ac4
comments
ywangd Jul 27, 2025
d20856e
Simply how we kick off assigned queued shards for entries not seeing …
ywangd Jul 27, 2025
b1de4c6
Merge remote-tracking branch 'origin/main' into shard-snapshot-limit-…
ywangd Jul 27, 2025
57ac6aa
half the per-node-limit in stress IT
ywangd Jul 27, 2025
9ec50c1
Honor running shard-snapshot counting in processWaitingShardsAndRemov…
ywangd Jul 29, 2025
5bd6ca2
enable rebalance
ywangd Jul 31, 2025
b5c4907
Add random allocation filtering
ywangd Jul 31, 2025
713d86a
rename
ywangd Jul 31, 2025
8a95b99
Randomize and update per-node limit in stress IT
ywangd Aug 2, 2025
292649d
Actually randomize initial shard snapshot limit
ywangd Aug 2, 2025
4109412
Pre-compute hasAssignedQueuedShards
ywangd Aug 7, 2025
f5d3b64
No need for static method
ywangd Aug 11, 2025
4768d27
Tweak and add tests for PerNodeShardSnapshotCounter
ywangd Aug 11, 2025
c3c56d2
more test assertions
ywangd Aug 11, 2025
6745235
Merge remote-tracking branch 'origin/main' into shard-snapshot-limit-…
ywangd Aug 12, 2025
287aca2
update changelog
ywangd Aug 12, 2025
55d4502
bwc default
ywangd Aug 12, 2025
6277c7c
Merge remote-tracking branch 'origin/main' into shard-snapshot-limit-…
ywangd Aug 14, 2025
dfb035f
augument tests for PerNodeShardSnapshotCounter
ywangd Aug 14, 2025
e2ef7e4
Add test for completion order
ywangd Aug 14, 2025
095010f
Merge remote-tracking branch 'origin/main' into shard-snapshot-limit-…
ywangd Aug 19, 2025
1337704
sometimes neither shutdown nor allocation filter
ywangd Aug 19, 2025
b9e4925
reinstate class qualification
ywangd Aug 19, 2025
fe7be7a
Use null nodeId to different aborted assigned-queued
ywangd Aug 19, 2025
99c81e7
fix test
ywangd Aug 19, 2025
1b90ef9
fix test
ywangd Aug 19, 2025
dab105d
Merge remote-tracking branch 'origin/main' into shard-snapshot-limit-…
ywangd Aug 19, 2025
6996653
Merge remote-tracking branch 'origin/main' into shard-snapshot-limit-…
ywangd Aug 21, 2025
0b64258
Complete assigned-queued shard snapshots inline without one more upda…
ywangd Aug 21, 2025
8bef3f0
Merge branch 'main' into shard-snapshot-limit-init-poc
elasticmachine Aug 21, 2025
a29ba9b
Fix propagation for clones
ywangd Aug 21, 2025
1d7f484
Revert "Fix propagation for clones"
ywangd Aug 21, 2025
5d8c51e
Revert "Complete assigned-queued shard snapshots inline without one m…
ywangd Aug 21, 2025
cf96d35
Refactor and reuse EntryContext for propagation
ywangd Aug 21, 2025
0198d7b
fail assigned-queued shards directly in processExternalChanges
ywangd Aug 21, 2025
0b9c6f2
Comments, renames, assertions and deleted status update
ywangd Aug 22, 2025
68f8663
Fix deletion where it may need to start a previous deletion
ywangd Aug 22, 2025
65888c5
Merge branch 'main' into shard-snapshot-limit-init-poc
DaveCTurner Sep 15, 2025
096a9df
[CI] Update transport version definitions
Oct 2, 2025
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
5 changes: 5 additions & 0 deletions docs/changelog/131592.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 131592
summary: "[PoC] Limited number of shard snapshot in INIT state per node"
area: Snapshot/Restore
type: enhancement
issues: []
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
package org.elasticsearch.snapshots;

import org.elasticsearch.cluster.SnapshotsInProgress;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.core.CheckedRunnable;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.indices.IndicesService;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.snapshots.mockstore.MockRepository;
import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.test.transport.MockTransportService;

import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;

@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 0)
public class SnapshotAndRelocationIT extends AbstractSnapshotIntegTestCase {

@Override
protected Collection<Class<? extends Plugin>> nodePlugins() {
return Arrays.asList(MockTransportService.TestPlugin.class, MockRepository.Plugin.class);
}

@Override
protected Settings nodeSettings(int nodeOrdinal, Settings otherSettings) {
return Settings.builder()
.put(super.nodeSettings(nodeOrdinal, otherSettings))
.put(SnapshotsService.SHARD_SNAPSHOT_PER_NODE_LIMIT_SETTING.getKey(), 1)
.build();
}

public void testLimitingInitAndRelocationForAssignedQueueShards() throws Exception {
final String masterNode = internalCluster().startMasterOnlyNode();
final String dataNodeA = internalCluster().startDataOnlyNode();
final String dataNodeB = internalCluster().startDataOnlyNode();
final String repoName = "test-repo";
createRepository(repoName, "mock");

final AtomicBoolean delayOnce = new AtomicBoolean(false);
final AtomicReference<CheckedRunnable<Exception>> delayedAction = new AtomicReference<>();
final var delayedActionSetLatch = new CountDownLatch(1);
MockTransportService.getInstance(masterNode)
.addRequestHandlingBehavior(SnapshotsService.UPDATE_SNAPSHOT_STATUS_ACTION_NAME, (handler, request, channel, task) -> {
if (delayOnce.compareAndSet(false, true)) {
delayedAction.set(() -> handler.messageReceived(request, channel, task));
delayedActionSetLatch.countDown();
} else {
handler.messageReceived(request, channel, task);
}
});

final var numIndices = between(2, 4);
final var indexNames = IntStream.range(0, numIndices).mapToObj(i -> "index-" + i).toList();

for (var indexName : indexNames) {
createIndex(indexName, indexSettings(1, 0).put("index.routing.allocation.include._name", dataNodeA).build());
indexRandomDocs(indexName, between(10, 42));
}
ensureGreen();

final var future = startFullSnapshot(repoName, "snapshot");
safeAwait(delayedActionSetLatch);

final var clusterService = internalCluster().getCurrentMasterNodeInstance(ClusterService.class);
final SnapshotsInProgress.Entry snapshot = SnapshotsInProgress.get(clusterService.state()).asStream().iterator().next();
logger.info("--> snapshot=[{}]", snapshot);
final var shards = snapshot.shards();
assertThat(shards.size(), equalTo(numIndices));

final var dataNodeAId = getNodeId(dataNodeA);
final var initShards = shards.entrySet()
.stream()
.filter(entry -> entry.getValue().state() == SnapshotsInProgress.ShardState.INIT)
.peek(entry -> assertThat(entry.getValue().nodeId(), equalTo(dataNodeAId)))
.map(Map.Entry::getKey)
.toList();
logger.info("--> init shards [{}]", initShards);
assertThat(initShards.size(), equalTo(1));

final var assignedQueuedShards = shards.entrySet()
.stream()
.filter(entry -> entry.getValue().isAssignedQueued())
.peek(entry -> assertThat(entry.getValue().nodeId(), equalTo(dataNodeAId)))
.map(Map.Entry::getKey)
.toList();
logger.info("--> assigned queued shards [{}]", assignedQueuedShards);
assertThat(assignedQueuedShards.size(), equalTo(numIndices - 1));

// Relocate indices that are assigned queued
final String[] indices = assignedQueuedShards.stream().map(ShardId::getIndexName).toArray(String[]::new);
logger.info("--> relocate indices [{}]", Arrays.toString(indices));
updateIndexSettings(Settings.builder().put("index.routing.allocation.include._name", dataNodeB), indices);
ensureGreen(indices);

final var dataNodeBIndicesService = internalCluster().getInstance(IndicesService.class, dataNodeB);
for (var shardId : assignedQueuedShards) {
assertTrue(
"indices: "
+ StreamSupport.stream(dataNodeBIndicesService.spliterator(), false)
.map(indexService -> indexService.index().getName())
.toList(),
dataNodeBIndicesService.hasIndex(shardId.getIndex())
);
}

assertThat(future.isDone(), is(false));
logger.info("--> run delayed action");
delayedAction.get().run();
assertSuccessful(future);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.stream.Stream;

import static org.elasticsearch.repositories.ProjectRepo.PROJECT_REPO_SERIALIZER;
Expand Down Expand Up @@ -197,6 +198,10 @@ public int count() {
return count;
}

public Set<ProjectRepo> repos() {
return entries.keySet();
}

public Iterable<List<Entry>> entriesByRepo() {
return () -> Iterators.map(entries.values().iterator(), byRepo -> byRepo.entries);
}
Expand Down Expand Up @@ -504,7 +509,7 @@ private static boolean assertShardStateConsistent(
int shardId,
ShardSnapshotStatus shardSnapshotStatus
) {
if (shardSnapshotStatus.isActive()) {
if (shardSnapshotStatus.isActiveOrAssignedQueued()) {
Tuple<String, Integer> plainShardId = Tuple.tuple(indexName, shardId);
assert assignedShards.add(plainShardId) : plainShardId + " is assigned twice in " + entries;
assert queuedShards.contains(plainShardId) == false : plainShardId + " is queued then assigned in " + entries;
Expand Down Expand Up @@ -752,6 +757,20 @@ public static ShardSnapshotStatus success(String nodeId, ShardSnapshotResult sha
return new ShardSnapshotStatus(nodeId, ShardState.SUCCESS, shardSnapshotResult.getGeneration(), null, shardSnapshotResult);
}

@SuppressForbidden(reason = "using a private constructor within the same file")
public static ShardSnapshotStatus assignedQueued(String nodeId, ShardGeneration generation) {
return new ShardSnapshotStatus(nodeId, ShardState.QUEUED, generation, null, null);
}

public boolean isAssignedQueued() {
// generation can still be null if previous shard snapshots all failed
return state == ShardState.QUEUED && nodeId != null;
Comment on lines +787 to +789
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the distinction between assigned-queued and unassigned-queued important? My thinking is that the point of leaving the shards in state QUEUED while a limit is in play is that we haven't committed to running them on a particular node yet: we only need to make that decision when we move them to INIT.

Copy link
Member Author

@ywangd ywangd Aug 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes the distinction is important. My general principle is to have assigned-queued work similarly to the INIT state as much as possible. I think this helps leverage existing code logic the most.

  1. When a new shard snapshot starts, we decide its state depend on whether there is already an inflight snapshot. An assigned-queued shard is considered inflight the same way as INIT so that the new shard snapshot will be in QUEUED in this state.
  2. When an assigned-queued shard snapshot fails due to abort or deleted shard, we cannot fail it directly because we also need to update any later snapshots that are in QUEUED state. This is done by sending the update to SnapshotTaskExecutor which performs the state propogation. Again, a similar approach for updating the INIT state.
  3. A snapshot deletion cannot start if there are assigned-queued shard snapshots similar to INIT ones.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I really don't like moving from QUEUED to ABORTED - ABORTED really means "aborting" - it indicates there's work in progress on the data node and we must wait for the data node to complete it and update the master state. But if the shard was QUEUED then we aren't waiting for the data node any more.

I see two alternatives:

  1. Extract out the common code that moves future snapshots of a shard to a ready (or at least assigned-queued) so we can re-use it on both paths.

  2. Drop the assigned-queued state and instead move shards from unassigned-queued to INIT in the SnapshotTaskExecutor as other shard snapshots complete.

What do you think?

Copy link
Member Author

@ywangd ywangd Aug 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ABORTED really means "aborting" - it indicates there's work in progress on the data node and we must wait for the data node to complete it and update the master state.

With this PR, we are giving new meanings to existing states. QUEUED can now be assigned and similarly ABORTED can now be unassigned, i.e. null nodeId indicates no work left on data node and master will complete it with another update cycle. These new meanings seem fine to me. We are adding more state transitions after all.

Extract out the common code that moves future snapshots of a shard to a ready (or at least assigned-queued) so we can re-use it on both paths.

I think the logic gets more complicated with this approach. We are basically duplicating part of SnapshotShardsUpdateContext#EntryContext#computeUpdatedSnapshotEntryFromShardUpdates and its downstream methods. It's very hard to extract a resuable code from it. We will have to duplicate. It's possible but I'd prefer we keep the states update in one place as suggested by this existing comment.

Drop the assigned-queued state and instead move shards from unassigned-queued to INIT in the SnapshotTaskExecutor as other shard snapshots complete.

Nof sure if I follow this suggestion. Do you mean completely remove the new assigned-queued state? I suspect the change would be a lot more complicated without it. We still need to differentiate one QUEUED shard from others. If we don't have the new state, we will have to rely on checking (1) whether the shard is the first queued and (2) whether there is a INIT shard before it. This is rather cubersome especially when we update snapshots with shard updates in a loop. Deletion also needs to tell the difference. Currently, QUEUED does not stop deletion from starting because there must be an INIT (or some other active state) shard prevents deletion from starting. This PR prevents deletion with assigned-queue state. Without it, deletion also needs to check whether one of the QUEUED shards is special. Overall, I think the new state makes the new logic to be more explicit. It is also likely more performant since we can quickly tell whether a snapshot has any shards queued due to the capacity limit. So I think I prefer it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear, if we must choose one alternative, I prefer the first one. It adds some complexity but with potential performance benefit, ie no need for another shard update cycle.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have now pushed 0b64258 to implement the first alternative. Please let me know whether it looks better. Thanks!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new change does not work because it does not handle clones. It's been a while and I forgot that was part of the complexities led me taking the original approach. I am trying to extend the change to cover clone. But it's getting quite ugly and I am not sure whether it is worthwhile.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I updated for a better implementation of the first alternative (cf96d35). It refactors and reuses EntryContext for propagating state changes of assigned-queued shards to later snapshots, i.e. it uses the same logic as ShardSnapshotUpdate tasks while avoiding extra cluster state update cycles. This is now ready for another look. Thanks!

}

public boolean isUnassignedQueued() {
return this == UNASSIGNED_QUEUED || (state == ShardState.QUEUED && generation == null && nodeId == null);
}

public ShardSnapshotStatus(
@Nullable String nodeId,
ShardState state,
Expand All @@ -772,8 +791,16 @@ private boolean assertConsistent() {
assert state.failed() == false || reason != null;
assert (state != ShardState.INIT && state != ShardState.WAITING && state != ShardState.PAUSED_FOR_NODE_REMOVAL)
|| nodeId != null : "Null node id for state [" + state + "]";
assert state != ShardState.QUEUED || (nodeId == null && generation == null && reason == null)
: "Found unexpected non-null values for queued state shard nodeId[" + nodeId + "][" + generation + "][" + reason + "]";
assert state != ShardState.QUEUED || (isUnassignedQueued() || isAssignedQueued())
: "Found unexpected shard state=["
+ state
+ "], nodeId=["
+ nodeId
+ "], generation=["
+ generation
+ "], reason=["
+ reason
+ "]";
assert state == ShardState.SUCCESS || shardSnapshotResult == null;
assert shardSnapshotResult == null || shardSnapshotResult.getGeneration().equals(generation)
: "generation [" + generation + "] does not match result generation [" + shardSnapshotResult.getGeneration() + "]";
Expand All @@ -787,7 +814,7 @@ public static ShardSnapshotStatus readFrom(StreamInput in) throws IOException {
final ShardGeneration generation = in.readOptionalWriteable(ShardGeneration::new);
final String reason = in.readOptionalString();
final ShardSnapshotResult shardSnapshotResult = in.readOptionalWriteable(ShardSnapshotResult::new);
if (state == ShardState.QUEUED) {
if (state == ShardState.QUEUED && nodeId == null && generation == null) {
return UNASSIGNED_QUEUED;
}
return new ShardSnapshotStatus(nodeId, state, generation, reason, shardSnapshotResult);
Expand Down Expand Up @@ -819,13 +846,18 @@ public ShardSnapshotResult shardSnapshotResult() {
* ({@link ShardState#INIT} or {@link ShardState#ABORTED}) or about to write to it in state {@link ShardState#WAITING} or
* {@link ShardState#PAUSED_FOR_NODE_REMOVAL}.
*/
// TODO: review its usage again
public boolean isActive() {
return switch (state) {
case INIT, ABORTED, WAITING, PAUSED_FOR_NODE_REMOVAL -> true;
case SUCCESS, FAILED, MISSING, QUEUED -> false;
};
}

public boolean isActiveOrAssignedQueued() {
return isActive() || isAssignedQueued();
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeOptionalString(nodeId);
Expand Down Expand Up @@ -1199,21 +1231,40 @@ public Entry withClones(Map<RepositoryShardId, ShardSnapshotStatus> updatedClone
* @return aborted snapshot entry or {@code null} if entry can be removed from the cluster state directly
*/
@Nullable
public Entry abort() {
public Entry abort(String localNodeId, BiConsumer<ShardId, ShardSnapshotStatus> abortedAssignedQueuedShardConsumer) {
final Map<ShardId, ShardSnapshotStatus> shardsBuilder = new HashMap<>();
boolean completed = true;
boolean allQueued = true;
for (Map.Entry<ShardId, ShardSnapshotStatus> shardEntry : shards.entrySet()) {
ShardSnapshotStatus status = shardEntry.getValue();
allQueued &= status.state() == ShardState.QUEUED;
final var isAssignedQueued = status.isAssignedQueued();
allQueued &= (status.state() == ShardState.QUEUED && isAssignedQueued == false);
if (status.state().completed() == false) {
final String nodeId = status.nodeId();
status = new ShardSnapshotStatus(
nodeId,
nodeId == null ? ShardState.FAILED : ShardState.ABORTED,
status.generation(),
"aborted by snapshot deletion"
);
if (isAssignedQueued == false) {
status = new ShardSnapshotStatus(
nodeId,
nodeId == null ? ShardState.FAILED : ShardState.ABORTED,
status.generation(),
"aborted by snapshot deletion"
);
} else {
assert isClone() == false
: "The state queued with generation should not be possible for a clone entry [" + this + "]";
final String reason = "assigned-queued aborted by snapshot deletion";
status = new ShardSnapshotStatus(
nodeId,
// Assigned QUEUED transitions to ABORTED (incomplete) and is completed by a separate cluster state update
ShardState.ABORTED,
status.generation(),
reason
);
// Accumulate the updates needed to complete the aborted QUEUED with generation shard snapshots
abortedAssignedQueuedShardConsumer.accept(
shardEntry.getKey(),
new ShardSnapshotStatus(localNodeId, ShardState.FAILED, status.generation, reason)
);
}
}
completed &= status.state().completed();
shardsBuilder.put(shardEntry.getKey(), status);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ private static Decision canMove(ShardRouting shardRouting, RoutingAllocation all
}
}

if (shardSnapshotStatus.isAssignedQueued()) {
continue;
}

return allocation.decision(
Decision.THROTTLE,
NAME,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,7 @@ public void apply(Settings value, Settings current, Settings previous) {
HandshakingTransportAddressConnector.PROBE_CONNECT_TIMEOUT_SETTING,
HandshakingTransportAddressConnector.PROBE_HANDSHAKE_TIMEOUT_SETTING,
SnapshotsService.MAX_CONCURRENT_SNAPSHOT_OPERATIONS_SETTING,
SnapshotsService.SHARD_SNAPSHOT_PER_NODE_LIMIT_SETTING,
RestoreService.REFRESH_REPO_UUID_ON_RESTORE_SETTING,
FsHealthService.ENABLED_SETTING,
FsHealthService.REFRESH_INTERVAL_SETTING,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ private static void addStateInformation(
int shardId,
String indexName
) {
if (shardState.isActive()) {
// Both active or assigned queued means the shard is meant to be the one with actions if node capacity allows it
if (shardState.isActiveOrAssignedQueued()) {
busyIds.computeIfAbsent(indexName, k -> new HashSet<>()).add(shardId);
assert assertGenerationConsistency(generations, indexName, shardId, shardState.generation());
} else if (shardState.state() == SnapshotsInProgress.ShardState.SUCCESS) {
Expand Down
Loading
Loading