-
Notifications
You must be signed in to change notification settings - Fork 24.9k
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
Delete backing snapshot when searchable snapshot index is deleted #75565
Changes from 7 commits
fdf484c
ba6b73b
a446683
704c8ea
955a5fc
982d6ea
4e641ab
c799368
e1995d7
e13a11a
4df53d7
3e8dc65
e1f17d5
b7bcf80
0bcaf26
83dfa73
eb0e89e
b1f36f8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,26 +16,40 @@ | |
import org.elasticsearch.cluster.AckedClusterStateUpdateTask; | ||
import org.elasticsearch.cluster.ClusterState; | ||
import org.elasticsearch.cluster.RestoreInProgress; | ||
import org.elasticsearch.cluster.SnapshotDeletionsInProgress; | ||
import org.elasticsearch.cluster.block.ClusterBlocks; | ||
import org.elasticsearch.cluster.routing.RoutingTable; | ||
import org.elasticsearch.cluster.routing.allocation.AllocationService; | ||
import org.elasticsearch.cluster.service.ClusterService; | ||
import org.elasticsearch.common.Priority; | ||
import org.elasticsearch.common.Strings; | ||
import org.elasticsearch.common.collect.ImmutableOpenMap; | ||
import org.elasticsearch.common.inject.Inject; | ||
import org.elasticsearch.common.settings.Settings; | ||
import org.elasticsearch.common.util.set.Sets; | ||
import org.elasticsearch.index.Index; | ||
import org.elasticsearch.repositories.RepositoryMissingException; | ||
import org.elasticsearch.snapshots.RestoreService; | ||
import org.elasticsearch.snapshots.SearchableSnapshotsSettings; | ||
import org.elasticsearch.snapshots.SnapshotId; | ||
import org.elasticsearch.snapshots.SnapshotInProgressException; | ||
import org.elasticsearch.snapshots.SnapshotsService; | ||
|
||
import java.util.Arrays; | ||
import java.util.HashMap; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.Set; | ||
|
||
import static java.util.Collections.emptyList; | ||
import static org.elasticsearch.snapshots.SearchableSnapshotsSettings.SEARCHABLE_SNAPSHOTS_DELETE_SNAPSHOT_ON_INDEX_DELETION; | ||
import static org.elasticsearch.snapshots.SearchableSnapshotsSettings.SEARCHABLE_SNAPSHOTS_REPOSITORY_NAME_SETTING_KEY; | ||
import static org.elasticsearch.snapshots.SearchableSnapshotsSettings.SEARCHABLE_SNAPSHOTS_REPOSITORY_UUID_SETTING_KEY; | ||
import static org.elasticsearch.snapshots.SearchableSnapshotsSettings.SEARCHABLE_SNAPSHOTS_SNAPSHOT_NAME_SETTING_KEY; | ||
import static org.elasticsearch.snapshots.SearchableSnapshotsSettings.SEARCHABLE_SNAPSHOTS_SNAPSHOT_UUID_SETTING_KEY; | ||
|
||
/** | ||
* Deletes indices. | ||
*/ | ||
|
@@ -120,6 +134,23 @@ public ClusterState deleteIndices(ClusterState currentState, Set<Index> indices) | |
logger.trace("{} tombstones purged from the cluster state. Previous tombstone size: {}. Current tombstone size: {}.", | ||
graveyardBuilder.getNumPurged(), previousGraveyardSize, currentGraveyard.getTombstones().size()); | ||
|
||
// add snapshot(s) marked as to delete to the cluster state | ||
final Map<String, Set<SnapshotId>> snapshotsToDelete = listOfSnapshotsToDelete(currentState, indicesToDelete); | ||
if (snapshotsToDelete.isEmpty() == false) { | ||
RepositoriesMetadata repositories = currentState.metadata().custom(RepositoriesMetadata.TYPE, RepositoriesMetadata.EMPTY); | ||
boolean changed = false; | ||
for (Map.Entry<String, Set<SnapshotId>> snapshotToDelete : snapshotsToDelete.entrySet()) { | ||
RepositoryMetadata repository = repositories.repository(snapshotToDelete.getKey()); | ||
if (repository != null) { | ||
repositories = repositories.addSnapshotsToDelete(repository.name(), snapshotToDelete.getValue()); | ||
changed = true; | ||
} | ||
} | ||
if (changed) { | ||
metadataBuilder.putCustom(RepositoriesMetadata.TYPE, repositories); | ||
} | ||
} | ||
|
||
Metadata newMetadata = metadataBuilder.build(); | ||
ClusterBlocks blocks = clusterBlocksBuilder.build(); | ||
|
||
|
@@ -142,4 +173,67 @@ public ClusterState deleteIndices(ClusterState currentState, Set<Index> indices) | |
.build(), | ||
"deleted indices [" + indices + "]"); | ||
} | ||
|
||
private static Map<String, Set<SnapshotId>> listOfSnapshotsToDelete(final ClusterState currentState, final Set<Index> indicesToDelete) { | ||
final Map<String, Set<SnapshotId>> snapshotsToDelete = new HashMap<>(); | ||
|
||
for (Index indexToDelete : indicesToDelete) { | ||
final Settings indexSettings = currentState.metadata().getIndexSafe(indexToDelete).getSettings(); | ||
if (SearchableSnapshotsSettings.isSearchableSnapshotIndexWithSnapshotDeletion(indexSettings) == false) { | ||
continue; | ||
} | ||
|
||
final String repositoryName = repositoryNameFromIndexSettings(currentState, indexSettings); | ||
final String snapshotName = indexSettings.get(SEARCHABLE_SNAPSHOTS_SNAPSHOT_NAME_SETTING_KEY); | ||
final String snapshotUuid = indexSettings.get(SEARCHABLE_SNAPSHOTS_SNAPSHOT_UUID_SETTING_KEY); | ||
|
||
boolean canDeleteSnapshot = true; | ||
|
||
// TODO change this to an assertion once it becomes impossible to delete a snapshot that is mounted as an index | ||
if (currentState.custom(SnapshotDeletionsInProgress.TYPE, SnapshotDeletionsInProgress.EMPTY) | ||
.getEntries().stream().anyMatch(entry -> entry.getSnapshots().contains(new SnapshotId(snapshotName, snapshotUuid)))) { | ||
continue; // this snapshot is part of an existing snapshot deletion in progress, nothing to do | ||
} | ||
|
||
for (IndexMetadata other : currentState.metadata()) { | ||
if (indicesToDelete.contains(other.getIndex())) { | ||
continue; // do not check indices that are going to be deleted | ||
} | ||
final Settings otherSettings = other.getSettings(); | ||
if (SearchableSnapshotsSettings.isSearchableSnapshotStore(otherSettings) == false) { | ||
continue; // other index is not a searchable snapshot index, skip | ||
} | ||
final String otherSnapshotUuid = otherSettings.get(SEARCHABLE_SNAPSHOTS_SNAPSHOT_UUID_SETTING_KEY); | ||
if (Objects.equals(snapshotUuid, otherSnapshotUuid) == false) { | ||
continue; // other index is backed by a different snapshot, skip | ||
} | ||
final String otherRepositoryName = repositoryNameFromIndexSettings(currentState, otherSettings); | ||
if (Objects.equals(repositoryName, otherRepositoryName) == false) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this might break in some odd corner cases involving registering the same repository under multiple names (maybe without repository UUIDs). But do we need to check this? By this point we know the snapshot UUID matches, that should be enough to tell us not to delete it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree and I removed this check in e1995d7 |
||
continue; // other index is backed by a snapshot from a different repository, skip | ||
} | ||
assert otherSettings.getAsBoolean(SEARCHABLE_SNAPSHOTS_DELETE_SNAPSHOT_ON_INDEX_DELETION, false) : other; | ||
DaveCTurner marked this conversation as resolved.
Show resolved
Hide resolved
|
||
canDeleteSnapshot = false; // another index is using the same snapshot, do not delete | ||
break; | ||
} | ||
if (canDeleteSnapshot) { | ||
snapshotsToDelete.computeIfAbsent(repositoryName, r -> new HashSet<>()) | ||
.add(new SnapshotId(indexSettings.get(SEARCHABLE_SNAPSHOTS_SNAPSHOT_NAME_SETTING_KEY), snapshotUuid)); | ||
} | ||
} | ||
return snapshotsToDelete; | ||
} | ||
|
||
private static String repositoryNameFromIndexSettings(ClusterState currentState, Settings indexSettings) { | ||
final String repositoryUuid = indexSettings.get(SEARCHABLE_SNAPSHOTS_REPOSITORY_UUID_SETTING_KEY); | ||
if (Strings.hasLength(repositoryUuid) == false) { | ||
return indexSettings.get(SEARCHABLE_SNAPSHOTS_REPOSITORY_NAME_SETTING_KEY); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we should return null in case there is a repo-uuid on the index but no such repo was found? This works differently from |
||
final RepositoriesMetadata repoMetadata = currentState.metadata().custom(RepositoriesMetadata.TYPE); | ||
final List<RepositoryMetadata> repositories = repoMetadata == null ? emptyList() : repoMetadata.repositories(); | ||
return repositories.stream() | ||
.filter(r -> repositoryUuid.equals(r.uuid())) | ||
.map(RepositoryMetadata::name) | ||
.findFirst() | ||
.orElseThrow(() -> new RepositoryMissingException(repositoryUuid)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be
null
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it can be null and actually we don't need to re read the repository metadata here so I pushed e1995d7