Skip to content

Ensure deletion of snapshot clone propagate state changes - #142192

Merged
elasticsearchmachine merged 20 commits into
elastic:mainfrom
ywangd:bugs-for-snapshot-deletion-and-clone
Feb 18, 2026
Merged

elasticsearchmachine merged 20 commits into
elastic:mainfrom
ywangd:bugs-for-snapshot-deletion-and-clone

Conversation

@ywangd

@ywangd ywangd commented Feb 10, 2026

Copy link
Copy Markdown
Member

Previously, when a clone is running with additional snapshots queued behind it, deleting the clone can leave the snapshots queued without starting the first queued. With this PR, we ensure the state is propogated correctly in such case so that the next inline snapshot or clone can run.

The PR also updates the stress tests to excercise aborting running clones as well as deleting completed ones.

@ywangd
ywangd requested a review from DaveCTurner February 10, 2026 07:17
@ywangd ywangd added >bug :Distributed/Snapshot/Restore Anything directly related to the `_snapshot/*` APIs v9.4.0 labels Feb 10, 2026
@elasticsearchmachine elasticsearchmachine added the Team:Distributed Meta label for distributed team. label Feb 10, 2026
@elasticsearchmachine

Copy link
Copy Markdown
Collaborator

Pinging @elastic/es-distributed (Team:Distributed)

@elasticsearchmachine

Copy link
Copy Markdown
Collaborator

Hi @ywangd, I've created a changelog YAML for you.

cloneName
);
if (abortSnapshot == false) {
snapshots.put(cloneName, new TrackedSnapshot(trackedRepository, cloneName));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Add clone to the list of completed snapshots so that it can deleted, restored and cloned again.

Comment on lines +1274 to +1282
// The clone is running, submit a request to master to FAIL it.
// It may race with SUCCESS. That's OK, we can take either final state.
statusUpdater.sendUpdate(
snapshot,
null,
shardEntry.getKey(),
new ShardSnapshotStatus(nodeId, ShardState.FAILED, status.generation(), "aborted by snapshot deletion"),
ActionListener.noop()
);

@ywangd ywangd Feb 10, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I chose to not update the state here. So that it will remain as INIT and get updated to FAILED by the update request. The alternative is to update it to ABORTED here as well as sending the update. I didn't do it because (1) A racing SUCCESS update can change ABORTED to SUCESS which is a bit strange. We can avoid it in applyShardSnapshotUpdate but it's extra code. (2) We don't use ABORTED for clones anywhere else. It was not necessary since it runs on the master. So in summary, I decided to keep the state unchanged here which feels overall simpler.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we should be working towards making clones behave more similarly to regular snapshots wherever possible, and this moves us in the opposite direction. We can do away with a fair amount of this special-case handling if the master-as-cluster-state-custodian interacted with the master-as-shard-level-cloner more like how it interacts with data nodes taking shard-level snapshots.

To that end I'd prefer we used the ABORTED state "properly" here. FAILED generally means that nothing is writing to this location in the repository any more (ignoring nodes which left the cluster) but here AIUI we might mark the shard as FAILED even while its clone operation is in progress. In particular I think we could also fix this bug by marking the shard as ABORTED here and then teaching the shard-level cloner to react to this properly and move to FAILED (or SUCCESS) when its work is done.

Moving from ABORTED to SUCCESS is legitimate, we have that transition on non-clone snapshots too. Possibly that deserves a comment somewhere.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I pushed 3c4f215 based on your suggestion. Does it match what you have in mind? We still need one if/else to handle the clone entry since it uses repoShardId instead of shardId.

Moving from ABORTED to SUCCESS is legitimate

Indeed, I missed it. The method name IndexShardSnapshotStatus#abortIfNotCompleted already indicates that.

@DaveCTurner DaveCTurner changed the title Ensure deletion of snapshot clone propogate state changes Ensure deletion of snapshot clone propagate state changes Feb 10, 2026

@DaveCTurner DaveCTurner left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks Yang, nice catch. I'm not sure this is quite how I'd want to fix this tho - see comments.

Comment thread docs/changelog/142192.yaml Outdated
}

private Runnable createAbortRunnable(boolean abortSnapshot, TrackedRepository trackedRepository, String snapshotName) {
final boolean isClone = snapshotName.contains("-clone-");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't really like this, but it seems it's only there for slightly more refined logs. Yet the log messages contain the snapshot name anyway. I think I'd rather stick with snapshot throughout (a clone is a special kind of snapshot) instead of doing this string-matching.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sure I removed it. Added it originally because the logs in startCloner do spell out clone.

private Runnable createAbortRunnable(boolean abortSnapshot, TrackedRepository trackedRepository, String snapshotName) {
final boolean isClone = snapshotName.contains("-clone-");
final Runnable abortRunnable;
if (abortSnapshot) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Possible bikeshedding but also not a fan of this. createAbortRunnable sounds like it will create a runnable which aborts the snapshot. Possibly renaming it to maybeCreateAbortRunnable is enough? Or move this branch back out to the callers?

If we do keep it here, could we have a constant NO_OP_ABORT_RUNNABLE = () -> {} and check for equality with that rather than having a separate bool?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah good point. I renamed the method and used a noop runnable for the check.

public void onFailure(Exception e) {
final Throwable cause = ExceptionsHelper.unwrapCause(e);
if (cause instanceof SnapshotException
&& Regex.simpleMatch("*" + cloneName + "*Snapshot was aborted by deletion", cause.getMessage())) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Bleurgh why is this reported specially? 🤦

Could we use org.elasticsearch.cluster.SnapshotsInProgress#ABORTED_FAILURE_TEXT rather than just the literal message?

Can we also check abortSnapshot in this if condition?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I used ABORTED_FAILURE_TEXT as suggested. Also add check for the abort runnable.

why is this reported specially

Not sure I follow. Do you mean we should remove the message match or something else?

Comment on lines +1274 to +1282
// The clone is running, submit a request to master to FAIL it.
// It may race with SUCCESS. That's OK, we can take either final state.
statusUpdater.sendUpdate(
snapshot,
null,
shardEntry.getKey(),
new ShardSnapshotStatus(nodeId, ShardState.FAILED, status.generation(), "aborted by snapshot deletion"),
ActionListener.noop()
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we should be working towards making clones behave more similarly to regular snapshots wherever possible, and this moves us in the opposite direction. We can do away with a fair amount of this special-case handling if the master-as-cluster-state-custodian interacted with the master-as-shard-level-cloner more like how it interacts with data nodes taking shard-level snapshots.

To that end I'd prefer we used the ABORTED state "properly" here. FAILED generally means that nothing is writing to this location in the repository any more (ignoring nodes which left the cluster) but here AIUI we might mark the shard as FAILED even while its clone operation is in progress. In particular I think we could also fix this bug by marking the shard as ABORTED here and then teaching the shard-level cloner to react to this properly and move to FAILED (or SUCCESS) when its work is done.

Moving from ABORTED to SUCCESS is legitimate, we have that transition on non-clone snapshots too. Possibly that deserves a comment somewhere.

@ywangd
ywangd requested a review from DaveCTurner February 11, 2026 10:22

@DaveCTurner DaveCTurner left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, one nit

}
return Entry.createClone(
snapshot,
completed(clonesBuilder.values()) ? (hasFailures(clonesBuilder) ? State.FAILED : State.SUCCESS) : State.ABORTED,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we need to check hasFailures here or could we just say State.FAILED in the completed branch? AIUI State.SUCCESS means we finalize the clone and then delete it, whereas State.FAILED means we skip finalization, which will leak data on any shards that did succeed. We need to be ok with this kind of leak (it gets cleaned up by subsequent deletes) so I think we could make this slightly less branchy here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(yet another deviation in behaviour, we should finalize either way so that the follow-up delete does the cleanup, but again no action to take here, just a comment)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

could we just say State.FAILED in the completed branch

Yeah we can do that for simplicity. See a143e80

we should finalize either way

I didn't notice the difference behaviour in finalization. Thanks! Yeah consistency would be better.

@ywangd ywangd added the auto-merge-without-approval Automatically merge pull request when CI checks pass (NB doesn't wait for reviews!) label Feb 18, 2026
@elasticsearchmachine
elasticsearchmachine merged commit 0ec7dda into elastic:main Feb 18, 2026
35 checks passed
@ywangd
ywangd deleted the bugs-for-snapshot-deletion-and-clone branch February 18, 2026 02:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-merge-without-approval Automatically merge pull request when CI checks pass (NB doesn't wait for reviews!) >bug :Distributed/Snapshot/Restore Anything directly related to the `_snapshot/*` APIs Team:Distributed Meta label for distributed team. v9.4.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants