Skip to content

Fix queued snapshot assignments after partial snapshot fails due to delete #88470

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
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
6 changes: 6 additions & 0 deletions docs/changelog/88470.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 88470
summary: Fix queued snapshot assignments after partial snapshot fails due to delete
area: Snapshot/Restore
type: bug
issues:
- 86724
Original file line number Diff line number Diff line change
Expand Up @@ -2003,6 +2003,30 @@ public void testSnapshotAndCloneQueuedAfterMissingShard() throws Exception {
assertThat(snapshot2.get().getSnapshotInfo().state(), is(SnapshotState.PARTIAL));
}

public void testQueuedSnapshotAfterPartialWithIndexRecreate() throws Exception {
internalCluster().startNodes(3);
// create an index with a large number of shards so that the nodes will not be able to start all shard snapshots before the index
// is deleted
final Settings highShardCountSettings = indexSettingsNoReplicas(randomIntBetween(12, 24)).build();
final String index1 = "index-1";
createIndexWithContent(index1, highShardCountSettings);
final String index2 = "index-2";
createIndexWithContent(index2);
final String repoName = "test-repo";
createRepository(repoName, "mock");
final ActionFuture<CreateSnapshotResponse> partialFuture = startFullSnapshot(repoName, "partial-snapshot", true);
blockAllDataNodes(repoName);
waitForBlockOnAnyDataNode(repoName);
// recreate index and start full snapshot to test that shard state updates from the first partial snapshot are correctly are
// correctly applied to the second snapshot that will contain a different index by the same name
assertAcked(client().admin().indices().prepareDelete(index1).get());
createIndexWithContent(index1, highShardCountSettings);
final ActionFuture<CreateSnapshotResponse> nonPartialFuture = startFullSnapshot(repoName, "full-snapshot");
unblockAllDataNodes(repoName);
assertSuccessful(nonPartialFuture);
assertSuccessful(partialFuture);
}

private static void assertSnapshotStatusCountOnRepo(String otherBlockedRepoName, int count) {
final SnapshotsStatusResponse snapshotsStatusResponse = client().admin()
.cluster()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3283,12 +3283,7 @@ private void tryStartNextTaskAfterSnapshotUpdated(ShardId shardId, ShardSnapshot
repoShardId
);
} else {
startShardOperation(
shardsBuilder(),
updatedState.nodeId(),
updatedState.generation(),
entry.shardId(repoShardId)
);
startShardSnapshot(repoShardId, updatedState.generation());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

No big change here really, just extracted the code that re-computes where to run the snapshot since we don't need to do the isQueued check twice and used it here.

}
}
}
Expand All @@ -3298,32 +3293,35 @@ private void tryStartSnapshotAfterCloneFinish(RepositoryShardId repoShardId, Sha
assert entry.source() == null;
// current entry is a snapshot operation so we must translate the repository shard id to a routing shard id
if (isQueued(entry.shardsByRepoShardId().get(repoShardId))) {
final Index index = entry.indexByName(repoShardId.indexName());
assert index != null
: "index ["
+ repoShardId.index()
+ "] must exist in snapshot entry ["
+ entry
+ "] because it's a normal snapshot but did not";
// A clone was updated, so we must use the correct data node id for the reassignment as actual shard snapshot
final IndexRoutingTable indexRouting = currentState.routingTable().index(index);
final ShardRouting shardRouting;
if (indexRouting == null) {
shardRouting = null;
} else {
shardRouting = indexRouting.shard(repoShardId.shardId()).primaryShard();
}
final ShardSnapshotStatus shardSnapshotStatus = initShardSnapshotStatus(generation, shardRouting);
final ShardId routingShardId = shardRouting != null
? shardRouting.shardId()
: new ShardId(index, repoShardId.shardId());
if (shardSnapshotStatus.isActive()) {
startShardOperation(shardsBuilder(), routingShardId, shardSnapshotStatus);
} else {
// update to queued snapshot did not result in an actual update execution so we just record it but keep applying
// the update to e.g. fail all snapshots for a given shard if the primary for the shard went away
shardsBuilder().put(routingShardId, shardSnapshotStatus);
}
startShardSnapshot(repoShardId, generation);
}
}

private void startShardSnapshot(RepositoryShardId repoShardId, ShardGeneration generation) {
final Index index = entry.indexByName(repoShardId.indexName());
assert index != null
: "index ["
+ repoShardId.index()
+ "] must exist in snapshot entry ["
+ entry
+ "] because it's a normal snapshot but did not";
// work out the node to run the snapshot task on as it might have changed from the previous operation if it was a clone
// or there was a primary failover
final IndexRoutingTable indexRouting = currentState.routingTable().index(index);
final ShardRouting shardRouting;
if (indexRouting == null) {
shardRouting = null;
} else {
shardRouting = indexRouting.shard(repoShardId.shardId()).primaryShard();
}
final ShardSnapshotStatus shardSnapshotStatus = initShardSnapshotStatus(generation, shardRouting);
final ShardId routingShardId = shardRouting != null ? shardRouting.shardId() : new ShardId(index, repoShardId.shardId());
if (shardSnapshotStatus.isActive()) {
startShardOperation(shardsBuilder(), routingShardId, shardSnapshotStatus);
} else {
// update to queued snapshot did not result in an actual update execution so we just record it but keep applying
// the update to e.g. fail all snapshots for a given shard if the primary for the shard went away
shardsBuilder().put(routingShardId, shardSnapshotStatus);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,10 +352,10 @@ public void testCompletedSnapshotStartsNextSnapshot() throws Exception {
final SnapshotsInProgress.Entry completedSnapshot = snapshotsInProgress.forRepo(repoName).get(0);
assertThat(completedSnapshot.state(), is(SnapshotsInProgress.State.SUCCESS));
final SnapshotsInProgress.Entry startedSnapshot = snapshotsInProgress.forRepo(repoName).get(1);
assertThat(startedSnapshot.state(), is(SnapshotsInProgress.State.STARTED));
assertThat(startedSnapshot.state(), is(SnapshotsInProgress.State.SUCCESS));
final SnapshotsInProgress.ShardSnapshotStatus shardSnapshotStatus = startedSnapshot.shards().get(routingShardId);
assertThat(shardSnapshotStatus.state(), is(SnapshotsInProgress.ShardState.INIT));
assertThat(shardSnapshotStatus.nodeId(), is(dataNodeId));
assertThat(shardSnapshotStatus.state(), is(SnapshotsInProgress.ShardState.MISSING));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was broken before, the shard isn't assigned so it must not move to INIT
=> since there's no other shards the snapshot must complete right away as well.

assertNull(shardSnapshotStatus.nodeId());
assertIsNoop(updatedClusterState, completeShard);
}

Expand Down