Skip to content

Don't fail delete index API if an index is deleted during the request#138015

Merged
nielsbauman merged 4 commits intoelastic:mainfrom
nielsbauman:fix-missing-in-delete
Nov 13, 2025
Merged

Don't fail delete index API if an index is deleted during the request#138015
nielsbauman merged 4 commits intoelastic:mainfrom
nielsbauman:fix-missing-in-delete

Conversation

@nielsbauman
Copy link
Contributor

In some cases, an index may have already been deleted by the time we process the deletion cluster state update task. In that case, we can simply skip it instead of throwing an exception. Index name resolution will have failed in the transport action (or in security) if the index never existed.

Closes #137422

@nielsbauman nielsbauman added >bug :Data Management/Indices APIs DO NOT USE. Use ":Distributed/Indices APIs" or ":StorageEngine/Templates" instead. labels Nov 13, 2025
@elasticsearchmachine elasticsearchmachine added v9.3.0 Team:Data Management (obsolete) DO NOT USE. This team no longer exists. labels Nov 13, 2025
@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/es-data-management (Team:Data Management)

@elasticsearchmachine
Copy link
Collaborator

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

Copy link
Member

@PeteGillinElastic PeteGillinElastic left a comment

Choose a reason for hiding this comment

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

Thanks!

byProject.computeIfAbsent(project.id(), ignore -> new HashSet<>()).add(index);
final Optional<ProjectMetadata> project = clusterState.metadata().lookupProject(index);
// In some cases, the index may have already been deleted by the time we process the deletion task. In that case, we can simply
// skip it. Index name resolution will have failed in the transport action (or in security) if the index never existed.
Copy link
Member

Choose a reason for hiding this comment

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

I think that the scenario is specifically when there's a wildcard delete request, and something else deletes the index between expanding the wildcard and doing the deletes, right? Maybe we could be more explicit about this here, I don't think I'd have realized that from this comment (I only got it from Mary's bug report).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought about including that, but I figured that it's technically also possible with two concurrent delete requests (with concrete index names) that both resolve the same indices and then both trigger a cluster state update. But I understand that the situation from the bug report isn't immediately obvious, so I'll add a comment suggesting that as an example.

if (project.isEmpty()) {
continue;
}
byProject.computeIfAbsent(project.get().id(), ignore -> new HashSet<>()).add(index);
Copy link
Member

Choose a reason for hiding this comment

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

Optional (no pun intended): I'm undecided whether a one-liner is more readable, or less:

project.map(ProjectMetadata::id).ifPresent(projectId -> byProject.computeIfAbsent(projectId, ignore -> new HashSet<>()).add(index));

WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think I have a very strong opinion here either. I'm usually a fan of one-liners, but I kind of like the explicitness of the "skip" here.

Copy link
Member

Choose a reason for hiding this comment

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

The purist would say that using Optional.get guarded by an ifPresent check is an anti-pattern, and we should always use things like ifPresent or orElse. But I'm not sure I agree... I'm happy enough with what you have here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I usually prefer to avoid any nested code blocks when I can, as I think that generally makes things easier to read (but it doesn't always apply).


for (final Map.Entry<ProjectId, Set<Index>> entry : byProject.entrySet()) {
// TODO Avoid creating the state multiple times if there are batched updates for multiple projects
clusterState = deleteIndices(clusterState.projectState(entry.getKey()), entry.getValue(), settings);
Copy link
Member

Choose a reason for hiding this comment

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

Thinking aloud: I was trying to figure out whether there's a small window in which the index could have been deleted between the check above and the deletion inside this method... I don't think there is, because that would have to be another cluster state update and those are serialized. The race condition only exists before the cluster state update starts. Does that sound right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, that's exactly right. If we're inside a cluster state update task, we can be sure that no other cluster state is being processed/created.

@nielsbauman nielsbauman enabled auto-merge (squash) November 13, 2025 17:47
@nielsbauman nielsbauman merged commit 6851803 into elastic:main Nov 13, 2025
34 checks passed
@nielsbauman nielsbauman deleted the fix-missing-in-delete branch November 13, 2025 18:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

>bug :Data Management/Indices APIs DO NOT USE. Use ":Distributed/Indices APIs" or ":StorageEngine/Templates" instead. Team:Data Management (obsolete) DO NOT USE. This team no longer exists. v9.3.0

3 participants