Skip to content

Commit 897ca9e

Browse files
Reduce Overhead of RepositoryData Cache for Large Repositories (#66587) (#66609)
This adds caching the fact that `RepositoryData` was too large to be cached so we don't serialize it over and over just to find out we can't cache it.
1 parent ee0394d commit 897ca9e

File tree

1 file changed

+65
-29
lines changed

1 file changed

+65
-29
lines changed

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

+65-29
Original file line numberDiff line numberDiff line change
@@ -678,10 +678,10 @@ public void onFailure(Exception e) {
678678
* @param rootBlobs Blobs at the repository root
679679
* @return RepositoryData
680680
*/
681-
private RepositoryData safeRepositoryData(long repositoryStateId, Map<String, BlobMetadata> rootBlobs) throws IOException {
681+
private RepositoryData safeRepositoryData(long repositoryStateId, Map<String, BlobMetadata> rootBlobs) {
682682
final long generation = latestGeneration(rootBlobs.keySet());
683683
final long genToLoad;
684-
final Tuple<Long, BytesReference> cached;
684+
final CachedRepositoryData cached;
685685
if (bestEffortConsistency) {
686686
genToLoad = latestKnownRepoGen.updateAndGet(known -> Math.max(known, repositoryStateId));
687687
cached = null;
@@ -700,8 +700,8 @@ private RepositoryData safeRepositoryData(long repositoryStateId, Map<String, Bl
700700
throw new RepositoryException(metadata.name(), "concurrent modification of the index-N file, expected current generation [" +
701701
repositoryStateId + "], actual current generation [" + genToLoad + "]");
702702
}
703-
if (cached != null && cached.v1() == genToLoad) {
704-
return repositoryDataFromCachedEntry(cached);
703+
if (cached != null && cached.generation() == genToLoad && cached.hasData()) {
704+
return cached.repositoryData();
705705
}
706706
return getRepositoryData(genToLoad);
707707
}
@@ -1288,7 +1288,6 @@ public String startVerification() {
12881288
String seed = UUIDs.randomBase64UUID();
12891289
byte[] testBytes = Strings.toUTF8Bytes(seed);
12901290
BlobContainer testContainer = blobStore().blobContainer(basePath().add(testBlobPrefix(seed)));
1291-
BytesArray bytes = new BytesArray(testBytes);
12921291
testContainer.writeBlobAtomic("master.dat", new BytesArray(testBytes), true);
12931292
return seed;
12941293
}
@@ -1314,20 +1313,62 @@ public void endVerification(String seed) {
13141313
private final AtomicLong latestKnownRepoGen = new AtomicLong(RepositoryData.UNKNOWN_REPO_GEN);
13151314

13161315
// Best effort cache of the latest known repository data and its generation, cached serialized as compressed json
1317-
private final AtomicReference<Tuple<Long, BytesReference>> latestKnownRepositoryData = new AtomicReference<>();
1316+
private final AtomicReference<CachedRepositoryData> latestKnownRepositoryData =
1317+
new AtomicReference<>(new CachedRepositoryData(RepositoryData.EMPTY_REPO_GEN, null));
1318+
1319+
/**
1320+
* Cached serialized repository data or placeholder to keep track of the fact that data for a generation was too large to be cached.
1321+
*/
1322+
private static final class CachedRepositoryData {
1323+
1324+
private final long generation;
1325+
1326+
@Nullable
1327+
private final BytesReference repositoryData;
1328+
1329+
CachedRepositoryData(long generation, @Nullable BytesReference repositoryData) {
1330+
this.generation = generation;
1331+
this.repositoryData = repositoryData;
1332+
}
1333+
1334+
long generation() {
1335+
return generation;
1336+
}
1337+
1338+
boolean hasData() {
1339+
return generation == RepositoryData.EMPTY_REPO_GEN || repositoryData != null;
1340+
}
1341+
1342+
@Nullable
1343+
RepositoryData repositoryData() {
1344+
if (generation == RepositoryData.EMPTY_REPO_GEN) {
1345+
return RepositoryData.EMPTY;
1346+
}
1347+
if (repositoryData == null) {
1348+
return null;
1349+
}
1350+
try (InputStream input = CompressorFactory.COMPRESSOR.threadLocalInputStream(repositoryData.streamInput())) {
1351+
return RepositoryData.snapshotsFromXContent(
1352+
XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, input),
1353+
generation, false);
1354+
} catch (IOException e) {
1355+
throw new AssertionError("no actual IO happens here", e);
1356+
}
1357+
}
1358+
}
13181359

13191360
@Override
13201361
public void getRepositoryData(ActionListener<RepositoryData> listener) {
13211362
if (latestKnownRepoGen.get() == RepositoryData.CORRUPTED_REPO_GEN) {
13221363
listener.onFailure(corruptedStateException(null));
13231364
return;
13241365
}
1325-
final Tuple<Long, BytesReference> cached = latestKnownRepositoryData.get();
1366+
final CachedRepositoryData cached = latestKnownRepositoryData.get();
13261367
// Fast path loading repository data directly from cache if we're in fully consistent mode and the cache matches up with
13271368
// the latest known repository generation
1328-
if (bestEffortConsistency == false && cached != null && cached.v1() == latestKnownRepoGen.get()) {
1369+
if (bestEffortConsistency == false && cached.generation() == latestKnownRepoGen.get() && cached.hasData()) {
13291370
try {
1330-
listener.onResponse(repositoryDataFromCachedEntry(cached));
1371+
listener.onResponse(cached.repositoryData());
13311372
} catch (Exception e) {
13321373
listener.onFailure(e);
13331374
}
@@ -1357,26 +1398,28 @@ private void doGetRepositoryData(ActionListener<RepositoryData> listener) {
13571398
}
13581399
genToLoad = latestKnownRepoGen.updateAndGet(known -> Math.max(known, generation));
13591400
if (genToLoad > generation) {
1360-
logger.info("Determined repository generation [" + generation
1361-
+ "] from repository contents but correct generation must be at least [" + genToLoad + "]");
1401+
logger.info("Determined repository generation [{}] from repository contents but correct generation must be at " +
1402+
"least [{}]", generation, genToLoad);
13621403
}
13631404
} else {
13641405
// We only rely on the generation tracked in #latestKnownRepoGen which is exclusively updated from the cluster state
13651406
genToLoad = latestKnownRepoGen.get();
13661407
}
13671408
try {
1368-
final Tuple<Long, BytesReference> cached = latestKnownRepositoryData.get();
1409+
final CachedRepositoryData cached = latestKnownRepositoryData.get();
13691410
final RepositoryData loaded;
13701411
// Caching is not used with #bestEffortConsistency see docs on #cacheRepositoryData for details
1371-
if (bestEffortConsistency == false && cached != null && cached.v1() == genToLoad) {
1372-
loaded = repositoryDataFromCachedEntry(cached);
1412+
if (bestEffortConsistency == false && cached.generation() == genToLoad && cached.hasData()) {
1413+
loaded = cached.repositoryData();
13731414
} else {
13741415
loaded = getRepositoryData(genToLoad);
1375-
// We can cache serialized in the most recent version here without regard to the actual repository metadata version
1376-
// since we're only caching the information that we just wrote and thus won't accidentally cache any information that
1377-
// isn't safe
1378-
cacheRepositoryData(compressRepoDataForCache(BytesReference.bytes(
1379-
loaded.snapshotsToXContent(XContentFactory.jsonBuilder(), Version.CURRENT))), genToLoad);
1416+
if (cached == null || cached.generation() < genToLoad) {
1417+
// We can cache serialized in the most recent version here without regard to the actual repository metadata version
1418+
// since we're only caching the information that we just wrote and thus won't accidentally cache any information
1419+
// that isn't safe
1420+
cacheRepositoryData(compressRepoDataForCache(BytesReference.bytes(
1421+
loaded.snapshotsToXContent(XContentFactory.jsonBuilder(), Version.CURRENT))), genToLoad);
1422+
}
13801423
}
13811424
listener.onResponse(loaded);
13821425
return;
@@ -1416,11 +1459,12 @@ private void doGetRepositoryData(ActionListener<RepositoryData> listener) {
14161459
* @param generation repository generation of the given repository data
14171460
*/
14181461
private void cacheRepositoryData(@Nullable BytesReference serialized, long generation) {
1462+
assert generation >= 0 : "No need to cache abstract generations but attempted to cache [" + generation + "]";
14191463
latestKnownRepositoryData.updateAndGet(known -> {
1420-
if (known != null && known.v1() > generation) {
1464+
if (known.generation() > generation) {
14211465
return known;
14221466
}
1423-
return serialized == null ? null : new Tuple<>(generation, serialized);
1467+
return new CachedRepositoryData(generation, serialized);
14241468
});
14251469
}
14261470

@@ -1456,14 +1500,6 @@ private BytesReference compressRepoDataForCache(BytesReference uncompressed) {
14561500
}
14571501
}
14581502

1459-
private RepositoryData repositoryDataFromCachedEntry(Tuple<Long, BytesReference> cacheEntry) throws IOException {
1460-
try (InputStream input = CompressorFactory.COMPRESSOR.threadLocalInputStream(cacheEntry.v2().streamInput())) {
1461-
return RepositoryData.snapshotsFromXContent(
1462-
XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY,
1463-
LoggingDeprecationHandler.INSTANCE, input), cacheEntry.v1(), false);
1464-
}
1465-
}
1466-
14671503
private RepositoryException corruptedStateException(@Nullable Exception cause) {
14681504
return new RepositoryException(metadata.name(),
14691505
"Could not read repository data because the contents of the repository do not match its " +

0 commit comments

Comments
 (0)