Skip to content

Commit 502d10f

Browse files
Reduce Overhead of RepositoryData Cache for Large Repositories (#66587)
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 82bfbe1 commit 502d10f

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

Lines changed: 65 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -662,10 +662,10 @@ public void onFailure(Exception e) {
662662
* @param rootBlobs Blobs at the repository root
663663
* @return RepositoryData
664664
*/
665-
private RepositoryData safeRepositoryData(long repositoryStateId, Map<String, BlobMetadata> rootBlobs) throws IOException {
665+
private RepositoryData safeRepositoryData(long repositoryStateId, Map<String, BlobMetadata> rootBlobs) {
666666
final long generation = latestGeneration(rootBlobs.keySet());
667667
final long genToLoad;
668-
final Tuple<Long, BytesReference> cached;
668+
final CachedRepositoryData cached;
669669
if (bestEffortConsistency) {
670670
genToLoad = latestKnownRepoGen.updateAndGet(known -> Math.max(known, repositoryStateId));
671671
cached = null;
@@ -684,8 +684,8 @@ private RepositoryData safeRepositoryData(long repositoryStateId, Map<String, Bl
684684
throw new RepositoryException(metadata.name(), "concurrent modification of the index-N file, expected current generation [" +
685685
repositoryStateId + "], actual current generation [" + genToLoad + "]");
686686
}
687-
if (cached != null && cached.v1() == genToLoad) {
688-
return repositoryDataFromCachedEntry(cached);
687+
if (cached != null && cached.generation() == genToLoad && cached.hasData()) {
688+
return cached.repositoryData();
689689
}
690690
return getRepositoryData(genToLoad);
691691
}
@@ -1272,7 +1272,6 @@ public String startVerification() {
12721272
String seed = UUIDs.randomBase64UUID();
12731273
byte[] testBytes = Strings.toUTF8Bytes(seed);
12741274
BlobContainer testContainer = blobStore().blobContainer(basePath().add(testBlobPrefix(seed)));
1275-
BytesArray bytes = new BytesArray(testBytes);
12761275
testContainer.writeBlobAtomic("master.dat", new BytesArray(testBytes), true);
12771276
return seed;
12781277
}
@@ -1298,20 +1297,62 @@ public void endVerification(String seed) {
12981297
private final AtomicLong latestKnownRepoGen = new AtomicLong(RepositoryData.UNKNOWN_REPO_GEN);
12991298

13001299
// Best effort cache of the latest known repository data and its generation, cached serialized as compressed json
1301-
private final AtomicReference<Tuple<Long, BytesReference>> latestKnownRepositoryData = new AtomicReference<>();
1300+
private final AtomicReference<CachedRepositoryData> latestKnownRepositoryData =
1301+
new AtomicReference<>(new CachedRepositoryData(RepositoryData.EMPTY_REPO_GEN, null));
1302+
1303+
/**
1304+
* Cached serialized repository data or placeholder to keep track of the fact that data for a generation was too large to be cached.
1305+
*/
1306+
private static final class CachedRepositoryData {
1307+
1308+
private final long generation;
1309+
1310+
@Nullable
1311+
private final BytesReference repositoryData;
1312+
1313+
CachedRepositoryData(long generation, @Nullable BytesReference repositoryData) {
1314+
this.generation = generation;
1315+
this.repositoryData = repositoryData;
1316+
}
1317+
1318+
long generation() {
1319+
return generation;
1320+
}
1321+
1322+
boolean hasData() {
1323+
return generation == RepositoryData.EMPTY_REPO_GEN || repositoryData != null;
1324+
}
1325+
1326+
@Nullable
1327+
RepositoryData repositoryData() {
1328+
if (generation == RepositoryData.EMPTY_REPO_GEN) {
1329+
return RepositoryData.EMPTY;
1330+
}
1331+
if (repositoryData == null) {
1332+
return null;
1333+
}
1334+
try (InputStream input = CompressorFactory.COMPRESSOR.threadLocalInputStream(repositoryData.streamInput())) {
1335+
return RepositoryData.snapshotsFromXContent(
1336+
XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, input),
1337+
generation, false);
1338+
} catch (IOException e) {
1339+
throw new AssertionError("no actual IO happens here", e);
1340+
}
1341+
}
1342+
}
13021343

13031344
@Override
13041345
public void getRepositoryData(ActionListener<RepositoryData> listener) {
13051346
if (latestKnownRepoGen.get() == RepositoryData.CORRUPTED_REPO_GEN) {
13061347
listener.onFailure(corruptedStateException(null));
13071348
return;
13081349
}
1309-
final Tuple<Long, BytesReference> cached = latestKnownRepositoryData.get();
1350+
final CachedRepositoryData cached = latestKnownRepositoryData.get();
13101351
// Fast path loading repository data directly from cache if we're in fully consistent mode and the cache matches up with
13111352
// the latest known repository generation
1312-
if (bestEffortConsistency == false && cached != null && cached.v1() == latestKnownRepoGen.get()) {
1353+
if (bestEffortConsistency == false && cached.generation() == latestKnownRepoGen.get() && cached.hasData()) {
13131354
try {
1314-
listener.onResponse(repositoryDataFromCachedEntry(cached));
1355+
listener.onResponse(cached.repositoryData());
13151356
} catch (Exception e) {
13161357
listener.onFailure(e);
13171358
}
@@ -1341,26 +1382,28 @@ private void doGetRepositoryData(ActionListener<RepositoryData> listener) {
13411382
}
13421383
genToLoad = latestKnownRepoGen.updateAndGet(known -> Math.max(known, generation));
13431384
if (genToLoad > generation) {
1344-
logger.info("Determined repository generation [" + generation
1345-
+ "] from repository contents but correct generation must be at least [" + genToLoad + "]");
1385+
logger.info("Determined repository generation [{}] from repository contents but correct generation must be at " +
1386+
"least [{}]", generation, genToLoad);
13461387
}
13471388
} else {
13481389
// We only rely on the generation tracked in #latestKnownRepoGen which is exclusively updated from the cluster state
13491390
genToLoad = latestKnownRepoGen.get();
13501391
}
13511392
try {
1352-
final Tuple<Long, BytesReference> cached = latestKnownRepositoryData.get();
1393+
final CachedRepositoryData cached = latestKnownRepositoryData.get();
13531394
final RepositoryData loaded;
13541395
// Caching is not used with #bestEffortConsistency see docs on #cacheRepositoryData for details
1355-
if (bestEffortConsistency == false && cached != null && cached.v1() == genToLoad) {
1356-
loaded = repositoryDataFromCachedEntry(cached);
1396+
if (bestEffortConsistency == false && cached.generation() == genToLoad && cached.hasData()) {
1397+
loaded = cached.repositoryData();
13571398
} else {
13581399
loaded = getRepositoryData(genToLoad);
1359-
// We can cache serialized in the most recent version here without regard to the actual repository metadata version
1360-
// since we're only caching the information that we just wrote and thus won't accidentally cache any information that
1361-
// isn't safe
1362-
cacheRepositoryData(compressRepoDataForCache(BytesReference.bytes(
1363-
loaded.snapshotsToXContent(XContentFactory.jsonBuilder(), Version.CURRENT))), genToLoad);
1400+
if (cached == null || cached.generation() < genToLoad) {
1401+
// We can cache serialized in the most recent version here without regard to the actual repository metadata version
1402+
// since we're only caching the information that we just wrote and thus won't accidentally cache any information
1403+
// that isn't safe
1404+
cacheRepositoryData(compressRepoDataForCache(BytesReference.bytes(
1405+
loaded.snapshotsToXContent(XContentFactory.jsonBuilder(), Version.CURRENT))), genToLoad);
1406+
}
13641407
}
13651408
listener.onResponse(loaded);
13661409
return;
@@ -1400,11 +1443,12 @@ private void doGetRepositoryData(ActionListener<RepositoryData> listener) {
14001443
* @param generation repository generation of the given repository data
14011444
*/
14021445
private void cacheRepositoryData(@Nullable BytesReference serialized, long generation) {
1446+
assert generation >= 0 : "No need to cache abstract generations but attempted to cache [" + generation + "]";
14031447
latestKnownRepositoryData.updateAndGet(known -> {
1404-
if (known != null && known.v1() > generation) {
1448+
if (known.generation() > generation) {
14051449
return known;
14061450
}
1407-
return serialized == null ? null : new Tuple<>(generation, serialized);
1451+
return new CachedRepositoryData(generation, serialized);
14081452
});
14091453
}
14101454

@@ -1440,14 +1484,6 @@ private BytesReference compressRepoDataForCache(BytesReference uncompressed) {
14401484
}
14411485
}
14421486

1443-
private RepositoryData repositoryDataFromCachedEntry(Tuple<Long, BytesReference> cacheEntry) throws IOException {
1444-
try (InputStream input = CompressorFactory.COMPRESSOR.threadLocalInputStream(cacheEntry.v2().streamInput())) {
1445-
return RepositoryData.snapshotsFromXContent(
1446-
XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY,
1447-
LoggingDeprecationHandler.INSTANCE, input), cacheEntry.v1(), false);
1448-
}
1449-
}
1450-
14511487
private RepositoryException corruptedStateException(@Nullable Exception cause) {
14521488
return new RepositoryException(metadata.name(),
14531489
"Could not read repository data because the contents of the repository do not match its " +

0 commit comments

Comments
 (0)