Skip to content

Commit e4ab1a7

Browse files
committed
Do not load global state when deleting a snapshot (#29278)
When deleting a snapshot, it is not necessary to load and to parse the global metadata of the snapshot to delete. Now indices are stored in the snapshot metadata file, we have all the information to resolve the shards files to delete. This commit removes the readSnapshotMetaData() method that was used to load both global and index metadata files. Test coverage should be enough as SharedClusterSnapshotRestoreIT already contains several deletion tests. Related to #28934
1 parent 9a065af commit e4ab1a7

File tree

3 files changed

+97
-64
lines changed

3 files changed

+97
-64
lines changed

server/src/main/java/org/elasticsearch/repositories/blobstore/BlobStoreRepository.java

Lines changed: 29 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -341,27 +341,17 @@ public void deleteSnapshot(SnapshotId snapshotId, long repositoryStateId) {
341341
if (isReadOnly()) {
342342
throw new RepositoryException(metadata.name(), "cannot delete snapshot from a readonly repository");
343343
}
344+
344345
final RepositoryData repositoryData = getRepositoryData();
345-
List<String> indices = Collections.emptyList();
346346
SnapshotInfo snapshot = null;
347347
try {
348348
snapshot = getSnapshotInfo(snapshotId);
349-
indices = snapshot.indices();
350349
} catch (SnapshotMissingException ex) {
351350
throw ex;
352351
} catch (IllegalStateException | SnapshotException | ElasticsearchParseException ex) {
353352
logger.warn(() -> new ParameterizedMessage("cannot read snapshot file [{}]", snapshotId), ex);
354353
}
355-
MetaData metaData = null;
356-
try {
357-
if (snapshot != null) {
358-
metaData = readSnapshotMetaData(snapshotId, snapshot.version(), repositoryData.resolveIndices(indices), true);
359-
} else {
360-
metaData = readSnapshotMetaData(snapshotId, null, repositoryData.resolveIndices(indices), true);
361-
}
362-
} catch (IOException | SnapshotException ex) {
363-
logger.warn(() -> new ParameterizedMessage("cannot read metadata for snapshot [{}]", snapshotId), ex);
364-
}
354+
365355
try {
366356
// Delete snapshot from the index file, since it is the maintainer of truth of active snapshots
367357
final RepositoryData updatedRepositoryData = repositoryData.removeSnapshot(snapshotId);
@@ -373,24 +363,29 @@ public void deleteSnapshot(SnapshotId snapshotId, long repositoryStateId) {
373363
deleteGlobalMetaDataBlobIgnoringErrors(snapshot, snapshotId.getUUID());
374364

375365
// Now delete all indices
376-
for (String index : indices) {
377-
final IndexId indexId = repositoryData.resolveIndexId(index);
378-
BlobPath indexPath = basePath().add("indices").add(indexId.getId());
379-
BlobContainer indexMetaDataBlobContainer = blobStore().blobContainer(indexPath);
380-
try {
381-
indexMetaDataFormat.delete(indexMetaDataBlobContainer, snapshotId.getUUID());
382-
} catch (IOException ex) {
383-
logger.warn(() -> new ParameterizedMessage("[{}] failed to delete metadata for index [{}]", snapshotId, index), ex);
384-
}
385-
if (metaData != null) {
386-
IndexMetaData indexMetaData = metaData.index(index);
366+
if (snapshot != null) {
367+
final List<String> indices = snapshot.indices();
368+
for (String index : indices) {
369+
final IndexId indexId = repositoryData.resolveIndexId(index);
370+
371+
IndexMetaData indexMetaData = null;
372+
try {
373+
indexMetaData = getSnapshotIndexMetaData(snapshotId, indexId);
374+
} catch (ElasticsearchParseException | IOException ex) {
375+
logger.warn(() ->
376+
new ParameterizedMessage("[{}] [{}] failed to read metadata for index", snapshotId, index), ex);
377+
}
378+
379+
deleteIndexMetaDataBlobIgnoringErrors(snapshot, indexId);
380+
387381
if (indexMetaData != null) {
388382
for (int shardId = 0; shardId < indexMetaData.getNumberOfShards(); shardId++) {
389383
try {
390384
delete(snapshotId, snapshot.version(), indexId, new ShardId(indexMetaData.getIndex(), shardId));
391385
} catch (SnapshotException ex) {
392386
final int finalShardId = shardId;
393-
logger.warn(() -> new ParameterizedMessage("[{}] failed to delete shard data for shard [{}][{}]", snapshotId, index, finalShardId), ex);
387+
logger.warn(() -> new ParameterizedMessage("[{}] failed to delete shard data for shard [{}][{}]",
388+
snapshotId, index, finalShardId), ex);
394389
}
395390
}
396391
}
@@ -448,6 +443,16 @@ private void deleteGlobalMetaDataBlobIgnoringErrors(final SnapshotInfo snapshotI
448443
}
449444
}
450445

446+
private void deleteIndexMetaDataBlobIgnoringErrors(final SnapshotInfo snapshotInfo, final IndexId indexId) {
447+
final SnapshotId snapshotId = snapshotInfo.snapshotId();
448+
BlobContainer indexMetaDataBlobContainer = blobStore().blobContainer(basePath().add("indices").add(indexId.getId()));
449+
try {
450+
indexMetaDataFormat.delete(indexMetaDataBlobContainer, snapshotId.getUUID());
451+
} catch (IOException ex) {
452+
logger.warn(() -> new ParameterizedMessage("[{}] failed to delete metadata for index [{}]", snapshotId, indexId.getName()), ex);
453+
}
454+
}
455+
451456
/**
452457
* {@inheritDoc}
453458
*/
@@ -508,44 +513,6 @@ public IndexMetaData getSnapshotIndexMetaData(final SnapshotId snapshotId, final
508513
return indexMetaDataFormat.read(blobStore().blobContainer(indexPath), snapshotId.getUUID());
509514
}
510515

511-
/**
512-
* Returns the global metadata associated with the snapshot.
513-
* <p>
514-
* The returned meta data contains global metadata as well as metadata
515-
* for all indices listed in the indices parameter.
516-
*/
517-
private MetaData readSnapshotMetaData(final SnapshotId snapshotId,
518-
final Version snapshotVersion,
519-
final List<IndexId> indices,
520-
final boolean ignoreErrors) throws IOException {
521-
if (snapshotVersion == null) {
522-
// When we delete corrupted snapshots we might not know which version we are dealing with
523-
// We can try detecting the version based on the metadata file format
524-
assert ignoreErrors;
525-
if (globalMetaDataFormat.exists(snapshotsBlobContainer, snapshotId.getUUID()) == false) {
526-
throw new SnapshotMissingException(metadata.name(), snapshotId);
527-
}
528-
}
529-
530-
final MetaData.Builder metaData = MetaData.builder(getSnapshotGlobalMetaData(snapshotId));
531-
if (indices != null) {
532-
for (IndexId index : indices) {
533-
try {
534-
metaData.put(getSnapshotIndexMetaData(snapshotId, index), false);
535-
} catch (ElasticsearchParseException | IOException ex) {
536-
if (ignoreErrors == false) {
537-
throw new SnapshotException(metadata.name(), snapshotId,
538-
"[" + index.getName() + "] failed to read metadata for index", ex);
539-
} else {
540-
logger.warn(() ->
541-
new ParameterizedMessage("[{}] [{}] failed to read metadata for index", snapshotId, index.getName()), ex);
542-
}
543-
}
544-
}
545-
}
546-
return metaData.build();
547-
}
548-
549516
/**
550517
* Configures RateLimiter based on repository and global settings
551518
*

server/src/test/java/org/elasticsearch/snapshots/MetadataLoadingDuringSnapshotRestoreIT.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,12 @@ public void testWhenMetadataAreLoaded() throws Exception {
139139
assertGlobalMetadataLoads("snap", 1);
140140
assertIndexMetadataLoads("snap", "docs", 4);
141141
assertIndexMetadataLoads("snap", "others", 3);
142+
143+
// Deleting a snapshot does not load the global metadata state but loads each index metadata
144+
assertAcked(client().admin().cluster().prepareDeleteSnapshot("repository", "snap").get());
145+
assertGlobalMetadataLoads("snap", 1);
146+
assertIndexMetadataLoads("snap", "docs", 5);
147+
assertIndexMetadataLoads("snap", "others", 4);
142148
}
143149

144150
private void assertGlobalMetadataLoads(final String snapshot, final int times) {

server/src/test/java/org/elasticsearch/snapshots/SharedClusterSnapshotRestoreIT.java

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,7 +1306,7 @@ public void testDeleteSnapshotWithMissingMetadata() throws Exception {
13061306
assertThat(createSnapshotResponse.getSnapshotInfo().successfulShards(), greaterThan(0));
13071307
assertThat(createSnapshotResponse.getSnapshotInfo().successfulShards(), equalTo(createSnapshotResponse.getSnapshotInfo().totalShards()));
13081308

1309-
logger.info("--> delete index metadata and shard metadata");
1309+
logger.info("--> delete global state metadata");
13101310
Path metadata = repo.resolve("meta-" + createSnapshotResponse.getSnapshotInfo().snapshotId().getUUID() + ".dat");
13111311
Files.delete(metadata);
13121312

@@ -1356,6 +1356,67 @@ public void testDeleteSnapshotWithCorruptedSnapshotFile() throws Exception {
13561356
assertThat(createSnapshotResponse.getSnapshotInfo().successfulShards(), equalTo(createSnapshotResponse.getSnapshotInfo().totalShards()));
13571357
}
13581358

1359+
/** Tests that a snapshot with a corrupted global state file can still be deleted */
1360+
public void testDeleteSnapshotWithCorruptedGlobalState() throws Exception {
1361+
final Path repo = randomRepoPath();
1362+
1363+
assertAcked(client().admin().cluster().preparePutRepository("test-repo")
1364+
.setType("fs")
1365+
.setSettings(Settings.builder()
1366+
.put("location", repo)
1367+
.put("chunk_size", randomIntBetween(100, 1000), ByteSizeUnit.BYTES)));
1368+
1369+
createIndex("test-idx-1", "test-idx-2");
1370+
indexRandom(true,
1371+
client().prepareIndex("test-idx-1", "_doc").setSource("foo", "bar"),
1372+
client().prepareIndex("test-idx-2", "_doc").setSource("foo", "bar"),
1373+
client().prepareIndex("test-idx-2", "_doc").setSource("foo", "bar"));
1374+
flushAndRefresh("test-idx-1", "test-idx-2");
1375+
1376+
CreateSnapshotResponse createSnapshotResponse = client().admin().cluster().prepareCreateSnapshot("test-repo", "test-snap")
1377+
.setIncludeGlobalState(true)
1378+
.setWaitForCompletion(true)
1379+
.get();
1380+
SnapshotInfo snapshotInfo = createSnapshotResponse.getSnapshotInfo();
1381+
assertThat(snapshotInfo.successfulShards(), greaterThan(0));
1382+
assertThat(snapshotInfo.successfulShards(), equalTo(snapshotInfo.totalShards()));
1383+
1384+
final Path globalStatePath = repo.resolve("meta-" + snapshotInfo.snapshotId().getUUID() + ".dat");
1385+
if (randomBoolean()) {
1386+
// Delete the global state metadata file
1387+
IOUtils.deleteFilesIgnoringExceptions(globalStatePath);
1388+
} else {
1389+
// Truncate the global state metadata file
1390+
try (SeekableByteChannel outChan = Files.newByteChannel(globalStatePath, StandardOpenOption.WRITE)) {
1391+
outChan.truncate(randomInt(10));
1392+
}
1393+
}
1394+
1395+
List<SnapshotInfo> snapshotInfos = client().admin().cluster().prepareGetSnapshots("test-repo").get().getSnapshots();
1396+
assertThat(snapshotInfos.size(), equalTo(1));
1397+
assertThat(snapshotInfos.get(0).state(), equalTo(SnapshotState.SUCCESS));
1398+
assertThat(snapshotInfos.get(0).snapshotId().getName(), equalTo("test-snap"));
1399+
1400+
SnapshotsStatusResponse snapshotStatusResponse =
1401+
client().admin().cluster().prepareSnapshotStatus("test-repo").setSnapshots("test-snap").get();
1402+
assertThat(snapshotStatusResponse.getSnapshots(), hasSize(1));
1403+
assertThat(snapshotStatusResponse.getSnapshots().get(0).getSnapshot().getSnapshotId().getName(), equalTo("test-snap"));
1404+
1405+
assertAcked(client().admin().cluster().prepareDeleteSnapshot("test-repo", "test-snap").get());
1406+
assertThrows(client().admin().cluster().prepareGetSnapshots("test-repo").addSnapshots("test-snap"),
1407+
SnapshotMissingException.class);
1408+
assertThrows(client().admin().cluster().prepareSnapshotStatus("test-repo").addSnapshots("test-snap"),
1409+
SnapshotMissingException.class);
1410+
1411+
createSnapshotResponse = client().admin().cluster().prepareCreateSnapshot("test-repo", "test-snap")
1412+
.setIncludeGlobalState(true)
1413+
.setWaitForCompletion(true)
1414+
.get();
1415+
snapshotInfo = createSnapshotResponse.getSnapshotInfo();
1416+
assertThat(snapshotInfo.successfulShards(), greaterThan(0));
1417+
assertThat(snapshotInfo.successfulShards(), equalTo(snapshotInfo.totalShards()));
1418+
}
1419+
13591420
public void testSnapshotWithMissingShardLevelIndexFile() throws Exception {
13601421
Path repo = randomRepoPath();
13611422
logger.info("--> creating repository at {}", repo.toAbsolutePath());
@@ -2642,7 +2703,6 @@ public void testRestoreSnapshotWithCorruptedGlobalState() throws Exception {
26422703
assertThat(snapshotInfo.successfulShards(), greaterThan(0));
26432704
assertThat(snapshotInfo.successfulShards(), equalTo(snapshotInfo.totalShards()));
26442705

2645-
// Truncate the global state metadata file
26462706
final Path globalStatePath = repo.resolve("meta-" + snapshotInfo.snapshotId().getUUID() + ".dat");
26472707
try(SeekableByteChannel outChan = Files.newByteChannel(globalStatePath, StandardOpenOption.WRITE)) {
26482708
outChan.truncate(randomInt(10));

0 commit comments

Comments
 (0)