Skip to content

Commit a0763d9

Browse files
Make RepositoryData Less Memory Heavy (#55293) (#55468)
We don't really need `LinkedHashSet` here. We can assume that all the entries are unique and just use a list and use the list utilities to create the cheapest possible version of the list. Also, this fixes a bug in `addSnapshot` which would mutate the existing linked hash set on the current instance (fortunately this never caused a real world bug) and brings the collection in line with the java docs on its getter that claim immutability.
1 parent e0195fa commit a0763d9

File tree

4 files changed

+43
-32
lines changed

4 files changed

+43
-32
lines changed

server/src/main/java/org/elasticsearch/repositories/RepositoryData.java

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,10 @@
3636
import java.util.Collection;
3737
import java.util.Collections;
3838
import java.util.HashMap;
39-
import java.util.LinkedHashSet;
39+
import java.util.HashSet;
4040
import java.util.List;
4141
import java.util.Map;
4242
import java.util.Objects;
43-
import java.util.Set;
4443
import java.util.function.Function;
4544
import java.util.stream.Collectors;
4645

@@ -90,7 +89,7 @@ public final class RepositoryData {
9089
/**
9190
* The snapshots that each index belongs to.
9291
*/
93-
private final Map<IndexId, Set<SnapshotId>> indexSnapshots;
92+
private final Map<IndexId, List<SnapshotId>> indexSnapshots;
9493

9594
private final Map<String, Version> snapshotVersions;
9695

@@ -100,7 +99,7 @@ public final class RepositoryData {
10099
private final ShardGenerations shardGenerations;
101100

102101
public RepositoryData(long genId, Map<String, SnapshotId> snapshotIds, Map<String, SnapshotState> snapshotStates,
103-
Map<String, Version> snapshotVersions, Map<IndexId, Set<SnapshotId>> indexSnapshots,
102+
Map<String, Version> snapshotVersions, Map<IndexId, List<SnapshotId>> indexSnapshots,
104103
ShardGenerations shardGenerations) {
105104
this.genId = genId;
106105
this.snapshotIds = Collections.unmodifiableMap(snapshotIds);
@@ -112,6 +111,8 @@ public RepositoryData(long genId, Map<String, SnapshotId> snapshotIds, Map<Strin
112111
this.snapshotVersions = snapshotVersions;
113112
assert indices.values().containsAll(shardGenerations.indices()) : "ShardGenerations contained indices "
114113
+ shardGenerations.indices() + " but snapshots only reference indices " + indices.values();
114+
assert indexSnapshots.values().stream().noneMatch(snapshotIdList -> new HashSet<>(snapshotIdList).size() != snapshotIdList.size()) :
115+
"Found duplicate snapshot ids per index in [" + indexSnapshots + "]";
115116
}
116117

117118
protected RepositoryData copy() {
@@ -213,9 +214,17 @@ public RepositoryData addSnapshot(final SnapshotId snapshotId,
213214
newSnapshotStates.put(snapshotId.getUUID(), snapshotState);
214215
Map<String, Version> newSnapshotVersions = new HashMap<>(snapshotVersions);
215216
newSnapshotVersions.put(snapshotId.getUUID(), version);
216-
Map<IndexId, Set<SnapshotId>> allIndexSnapshots = new HashMap<>(indexSnapshots);
217+
Map<IndexId, List<SnapshotId>> allIndexSnapshots = new HashMap<>(indexSnapshots);
217218
for (final IndexId indexId : shardGenerations.indices()) {
218-
allIndexSnapshots.computeIfAbsent(indexId, k -> new LinkedHashSet<>()).add(snapshotId);
219+
final List<SnapshotId> snapshotIds = allIndexSnapshots.get(indexId);
220+
if (snapshotIds == null) {
221+
allIndexSnapshots.put(indexId, Collections.singletonList(snapshotId));
222+
} else {
223+
final List<SnapshotId> copy = new ArrayList<>(snapshotIds.size() + 1);
224+
copy.addAll(snapshotIds);
225+
copy.add(snapshotId);
226+
allIndexSnapshots.put(indexId, Collections.unmodifiableList(copy));
227+
}
219228
}
220229
return new RepositoryData(genId, snapshots, newSnapshotStates, newSnapshotVersions, allIndexSnapshots,
221230
ShardGenerations.builder().putAll(this.shardGenerations).putAll(shardGenerations).build());
@@ -253,23 +262,25 @@ public RepositoryData removeSnapshot(final SnapshotId snapshotId, final ShardGen
253262
newSnapshotStates.remove(snapshotId.getUUID());
254263
final Map<String, Version> newSnapshotVersions = new HashMap<>(snapshotVersions);
255264
newSnapshotVersions.remove(snapshotId.getUUID());
256-
Map<IndexId, Set<SnapshotId>> indexSnapshots = new HashMap<>();
265+
Map<IndexId, List<SnapshotId>> indexSnapshots = new HashMap<>();
257266
for (final IndexId indexId : indices.values()) {
258-
Set<SnapshotId> set;
259-
Set<SnapshotId> snapshotIds = this.indexSnapshots.get(indexId);
267+
List<SnapshotId> remaining;
268+
List<SnapshotId> snapshotIds = this.indexSnapshots.get(indexId);
260269
assert snapshotIds != null;
261-
if (snapshotIds.contains(snapshotId)) {
270+
final int listIndex = snapshotIds.indexOf(snapshotId);
271+
if (listIndex > -1) {
262272
if (snapshotIds.size() == 1) {
263273
// removing the snapshot will mean no more snapshots
264274
// have this index, so just skip over it
265275
continue;
266276
}
267-
set = new LinkedHashSet<>(snapshotIds);
268-
set.remove(snapshotId);
277+
remaining = new ArrayList<>(snapshotIds);
278+
remaining.remove(listIndex);
279+
remaining = Collections.unmodifiableList(remaining);
269280
} else {
270-
set = snapshotIds;
281+
remaining = snapshotIds;
271282
}
272-
indexSnapshots.put(indexId, set);
283+
indexSnapshots.put(indexId, remaining);
273284
}
274285

275286
return new RepositoryData(genId, newSnapshotIds, newSnapshotStates, newSnapshotVersions, indexSnapshots,
@@ -281,8 +292,8 @@ public RepositoryData removeSnapshot(final SnapshotId snapshotId, final ShardGen
281292
/**
282293
* Returns an immutable collection of the snapshot ids for the snapshots that contain the given index.
283294
*/
284-
public Set<SnapshotId> getSnapshots(final IndexId indexId) {
285-
Set<SnapshotId> snapshotIds = indexSnapshots.get(indexId);
295+
public List<SnapshotId> getSnapshots(final IndexId indexId) {
296+
List<SnapshotId> snapshotIds = indexSnapshots.get(indexId);
286297
if (snapshotIds == null) {
287298
throw new IllegalArgumentException("unknown snapshot index " + indexId);
288299
}
@@ -384,7 +395,7 @@ public XContentBuilder snapshotsToXContent(final XContentBuilder builder, final
384395
builder.startObject(indexId.getName());
385396
builder.field(INDEX_ID, indexId.getId());
386397
builder.startArray(SNAPSHOTS);
387-
Set<SnapshotId> snapshotIds = indexSnapshots.get(indexId);
398+
List<SnapshotId> snapshotIds = indexSnapshots.get(indexId);
388399
assert snapshotIds != null;
389400
for (final SnapshotId snapshotId : snapshotIds) {
390401
builder.value(snapshotId.getUUID());
@@ -415,7 +426,7 @@ public static RepositoryData snapshotsFromXContent(final XContentParser parser,
415426
final Map<String, SnapshotId> snapshots = new HashMap<>();
416427
final Map<String, SnapshotState> snapshotStates = new HashMap<>();
417428
final Map<String, Version> snapshotVersions = new HashMap<>();
418-
final Map<IndexId, Set<SnapshotId>> indexSnapshots = new HashMap<>();
429+
final Map<IndexId, List<SnapshotId>> indexSnapshots = new HashMap<>();
419430
final ShardGenerations.Builder shardGenerations = ShardGenerations.builder();
420431

421432
if (parser.nextToken() == XContentParser.Token.START_OBJECT) {
@@ -459,7 +470,7 @@ public static RepositoryData snapshotsFromXContent(final XContentParser parser,
459470
}
460471
while (parser.nextToken() != XContentParser.Token.END_OBJECT) {
461472
final String indexName = parser.currentName();
462-
final Set<SnapshotId> snapshotIds = new LinkedHashSet<>();
473+
final List<SnapshotId> snapshotIds = new ArrayList<>();
463474
final List<String> gens = new ArrayList<>();
464475

465476
IndexId indexId = null;
@@ -513,7 +524,7 @@ public static RepositoryData snapshotsFromXContent(final XContentParser parser,
513524
}
514525
}
515526
assert indexId != null;
516-
indexSnapshots.put(indexId, snapshotIds);
527+
indexSnapshots.put(indexId, Collections.unmodifiableList(snapshotIds));
517528
for (int i = 0; i < gens.size(); i++) {
518529
shardGenerations.put(indexId, i, gens.get(i));
519530
}

server/src/test/java/org/elasticsearch/repositories/RepositoryDataTests.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public void testIndicesToUpdateAfterRemovingSnapshot() {
6464
final List<IndexId> indicesBefore = new ArrayList<>(repositoryData.getIndices().values());
6565
final SnapshotId randomSnapshot = randomFrom(repositoryData.getSnapshotIds());
6666
final IndexId[] indicesToUpdate = indicesBefore.stream().filter(index -> {
67-
final Set<SnapshotId> snapshotIds = repositoryData.getSnapshots(index);
67+
final List<SnapshotId> snapshotIds = repositoryData.getSnapshots(index);
6868
return snapshotIds.contains(randomSnapshot) && snapshotIds.size() > 1;
6969
}).toArray(IndexId[]::new);
7070
assertThat(repositoryData.indicesToUpdateAfterRemovingSnapshot(randomSnapshot), containsInAnyOrder(indicesToUpdate));
@@ -111,7 +111,7 @@ public void testAddSnapshots() {
111111
// verify that the new repository data has the new snapshot and its indices
112112
assertTrue(newRepoData.getSnapshotIds().contains(newSnapshot));
113113
for (IndexId indexId : indices) {
114-
Set<SnapshotId> snapshotIds = newRepoData.getSnapshots(indexId);
114+
List<SnapshotId> snapshotIds = newRepoData.getSnapshots(indexId);
115115
assertTrue(snapshotIds.contains(newSnapshot));
116116
if (newIndices.contains(indexId)) {
117117
assertEquals(snapshotIds.size(), 1); // if it was a new index, only the new snapshot should be in its set
@@ -134,7 +134,7 @@ public void testInitIndices() {
134134
RepositoryData repositoryData = new RepositoryData(EMPTY_REPO_GEN, snapshotIds,
135135
Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap(), ShardGenerations.EMPTY);
136136
// test that initializing indices works
137-
Map<IndexId, Set<SnapshotId>> indices = randomIndices(snapshotIds);
137+
Map<IndexId, List<SnapshotId>> indices = randomIndices(snapshotIds);
138138
RepositoryData newRepoData =
139139
new RepositoryData(repositoryData.getGenId(), snapshotIds, snapshotStates, snapshotVersions, indices, ShardGenerations.EMPTY);
140140
List<SnapshotId> expected = new ArrayList<>(repositoryData.getSnapshotIds());
@@ -201,11 +201,11 @@ public void testIndexThatReferencesAnUnknownSnapshot() throws IOException {
201201

202202
final IndexId corruptedIndexId = randomFrom(parsedRepositoryData.getIndices().values());
203203

204-
Map<IndexId, Set<SnapshotId>> indexSnapshots = new HashMap<>();
204+
Map<IndexId, List<SnapshotId>> indexSnapshots = new HashMap<>();
205205
final ShardGenerations.Builder shardGenBuilder = ShardGenerations.builder();
206206
for (Map.Entry<String, IndexId> snapshottedIndex : parsedRepositoryData.getIndices().entrySet()) {
207207
IndexId indexId = snapshottedIndex.getValue();
208-
Set<SnapshotId> snapshotsIds = new LinkedHashSet<>(parsedRepositoryData.getSnapshots(indexId));
208+
List<SnapshotId> snapshotsIds = new ArrayList<>(parsedRepositoryData.getSnapshots(indexId));
209209
if (corruptedIndexId.equals(indexId)) {
210210
snapshotsIds.add(new SnapshotId("_uuid", "_does_not_exist"));
211211
}
@@ -293,19 +293,19 @@ public static RepositoryData generateRandomRepoData() {
293293
return repositoryData;
294294
}
295295

296-
private static Map<IndexId, Set<SnapshotId>> randomIndices(final Map<String, SnapshotId> snapshotIdsMap) {
296+
private static Map<IndexId, List<SnapshotId>> randomIndices(final Map<String, SnapshotId> snapshotIdsMap) {
297297
final List<SnapshotId> snapshotIds = new ArrayList<>(snapshotIdsMap.values());
298298
final int totalSnapshots = snapshotIds.size();
299299
final int numIndices = randomIntBetween(1, 30);
300-
final Map<IndexId, Set<SnapshotId>> indices = new HashMap<>(numIndices);
300+
final Map<IndexId, List<SnapshotId>> indices = new HashMap<>(numIndices);
301301
for (int i = 0; i < numIndices; i++) {
302302
final IndexId indexId = new IndexId(randomAlphaOfLength(8), UUIDs.randomBase64UUID());
303303
final Set<SnapshotId> indexSnapshots = new LinkedHashSet<>();
304304
final int numIndicesForSnapshot = randomIntBetween(1, numIndices);
305305
for (int j = 0; j < numIndicesForSnapshot; j++) {
306306
indexSnapshots.add(snapshotIds.get(randomIntBetween(0, totalSnapshots - 1)));
307307
}
308-
indices.put(indexId, indexSnapshots);
308+
indices.put(indexId, Collections.unmodifiableList(new ArrayList<>(indexSnapshots)));
309309
}
310310
return indices;
311311
}

test/framework/src/main/java/org/elasticsearch/index/shard/RestoreOnlyRepository.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
import java.util.List;
4545
import java.util.Map;
4646

47-
import static java.util.Collections.emptySet;
47+
import static java.util.Collections.emptyList;
4848
import static org.elasticsearch.repositories.RepositoryData.EMPTY_REPO_GEN;
4949

5050
/** A dummy repository for testing which just needs restore overridden */
@@ -91,7 +91,7 @@ public IndexMetadata getSnapshotIndexMetadata(SnapshotId snapshotId, IndexId ind
9191
public void getRepositoryData(ActionListener<RepositoryData> listener) {
9292
final IndexId indexId = new IndexId(indexName, "blah");
9393
listener.onResponse(new RepositoryData(EMPTY_REPO_GEN, Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap(),
94-
Collections.singletonMap(indexId, emptySet()), ShardGenerations.EMPTY));
94+
Collections.singletonMap(indexId, emptyList()), ShardGenerations.EMPTY));
9595
}
9696

9797
@Override

x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/repository/CcrRepository.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ public void getRepositoryData(ActionListener<RepositoryData> listener) {
238238
Map<String, SnapshotId> copiedSnapshotIds = new HashMap<>();
239239
Map<String, SnapshotState> snapshotStates = new HashMap<>(copiedSnapshotIds.size());
240240
Map<String, Version> snapshotVersions = new HashMap<>(copiedSnapshotIds.size());
241-
Map<IndexId, Set<SnapshotId>> indexSnapshots = new HashMap<>(copiedSnapshotIds.size());
241+
Map<IndexId, List<SnapshotId>> indexSnapshots = new HashMap<>(copiedSnapshotIds.size());
242242

243243
ImmutableOpenMap<String, IndexMetadata> remoteIndices = remoteMetadata.getIndices();
244244
for (String indexName : remoteMetadata.getConcreteAllIndices()) {
@@ -248,7 +248,7 @@ public void getRepositoryData(ActionListener<RepositoryData> listener) {
248248
snapshotStates.put(indexName, SnapshotState.SUCCESS);
249249
snapshotVersions.put(indexName, Version.CURRENT);
250250
Index index = remoteIndices.get(indexName).getIndex();
251-
indexSnapshots.put(new IndexId(indexName, index.getUUID()), Collections.singleton(snapshotId));
251+
indexSnapshots.put(new IndexId(indexName, index.getUUID()), Collections.singletonList(snapshotId));
252252
}
253253
return new RepositoryData(1, copiedSnapshotIds, snapshotStates, snapshotVersions, indexSnapshots, ShardGenerations.EMPTY);
254254
});

0 commit comments

Comments
 (0)