Skip to content

Commit 8fc3f1c

Browse files
author
Ali Beyad
committed
Enhances get snapshots API to allow retrieving repository index only (#24477)
Currently, the get snapshots API (e.g. /_snapshot/{repositoryName}/_all) provides information about snapshots in the repository, including the snapshot state, number of shards snapshotted, failures, etc. In order to provide information about each snapshot in the repository, the call must read the snapshot metadata blob (`snap-{snapshot_uuid}.dat`) for every snapshot. In cloud-based repositories, this can be expensive, both from a cost and performance perspective. Sometimes, all the user wants is to retrieve all the names/uuids of each snapshot, and the indices that went into each snapshot, without any of the other status information about the snapshot. This minimal information can be retrieved from the repository index blob (`index-N`) without needing to read each snapshot metadata blob. This commit enhances the get snapshots API with an optional `verbose` parameter. If `verbose` is set to false on the request, then the get snapshots API will only retrieve the minimal information about each snapshot (the name, uuid, and indices in the snapshot), and only read this information from the repository index blob, thereby giving users the option to retrieve the snapshots in a repository in a more cost-effective and efficient manner. Closes #24288
1 parent 997767c commit 8fc3f1c

File tree

17 files changed

+512
-131
lines changed

17 files changed

+512
-131
lines changed

core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequest.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.io.IOException;
2929

3030
import static org.elasticsearch.action.ValidateActions.addValidationError;
31+
import static org.elasticsearch.snapshots.SnapshotInfo.VERBOSE_INTRODUCED;
3132

3233
/**
3334
* Get snapshot request
@@ -43,6 +44,8 @@ public class GetSnapshotsRequest extends MasterNodeRequest<GetSnapshotsRequest>
4344

4445
private boolean ignoreUnavailable;
4546

47+
private boolean verbose = true;
48+
4649
public GetSnapshotsRequest() {
4750
}
4851

@@ -123,19 +126,44 @@ public GetSnapshotsRequest ignoreUnavailable(boolean ignoreUnavailable) {
123126
this.ignoreUnavailable = ignoreUnavailable;
124127
return this;
125128
}
129+
126130
/**
127131
* @return Whether snapshots should be ignored when unavailable (corrupt or temporarily not fetchable)
128132
*/
129133
public boolean ignoreUnavailable() {
130134
return ignoreUnavailable;
131135
}
132136

137+
/**
138+
* Set to {@code false} to only show the snapshot names and the indices they contain.
139+
* This is useful when the snapshots belong to a cloud-based repository where each
140+
* blob read is a concern (cost wise and performance wise), as the snapshot names and
141+
* indices they contain can be retrieved from a single index blob in the repository,
142+
* whereas the rest of the information requires reading a snapshot metadata file for
143+
* each snapshot requested. Defaults to {@code true}, which returns all information
144+
* about each requested snapshot.
145+
*/
146+
public GetSnapshotsRequest verbose(boolean verbose) {
147+
this.verbose = verbose;
148+
return this;
149+
}
150+
151+
/**
152+
* Returns whether the request will return a verbose response.
153+
*/
154+
public boolean verbose() {
155+
return verbose;
156+
}
157+
133158
@Override
134159
public void readFrom(StreamInput in) throws IOException {
135160
super.readFrom(in);
136161
repository = in.readString();
137162
snapshots = in.readStringArray();
138163
ignoreUnavailable = in.readBoolean();
164+
if (in.getVersion().onOrAfter(VERBOSE_INTRODUCED)) {
165+
verbose = in.readBoolean();
166+
}
139167
}
140168

141169
@Override
@@ -144,5 +172,8 @@ public void writeTo(StreamOutput out) throws IOException {
144172
out.writeString(repository);
145173
out.writeStringArray(snapshots);
146174
out.writeBoolean(ignoreUnavailable);
175+
if (out.getVersion().onOrAfter(VERBOSE_INTRODUCED)) {
176+
out.writeBoolean(verbose);
177+
}
147178
}
148179
}

core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequestBuilder.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,18 @@ public GetSnapshotsRequestBuilder setIgnoreUnavailable(boolean ignoreUnavailable
9696
return this;
9797
}
9898

99+
/**
100+
* Set to {@code false} to only show the snapshot names and the indices they contain.
101+
* This is useful when the snapshots belong to a cloud-based repository where each
102+
* blob read is a concern (cost wise and performance wise), as the snapshot names and
103+
* indices they contain can be retrieved from a single index blob in the repository,
104+
* whereas the rest of the information requires reading a snapshot metadata file for
105+
* each snapshot requested. Defaults to {@code true}, which returns all information
106+
* about each requested snapshot.
107+
*/
108+
public GetSnapshotsRequestBuilder setVerbose(boolean verbose) {
109+
request.verbose(verbose);
110+
return this;
111+
}
112+
99113
}

core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/TransportGetSnapshotsAction.java

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.action.admin.cluster.snapshots.get;
2121

22+
import org.apache.lucene.util.CollectionUtil;
2223
import org.elasticsearch.action.ActionListener;
2324
import org.elasticsearch.action.support.ActionFilters;
2425
import org.elasticsearch.action.support.master.TransportMasterNodeAction;
@@ -30,6 +31,7 @@
3031
import org.elasticsearch.common.inject.Inject;
3132
import org.elasticsearch.common.regex.Regex;
3233
import org.elasticsearch.common.settings.Settings;
34+
import org.elasticsearch.repositories.IndexId;
3335
import org.elasticsearch.repositories.RepositoryData;
3436
import org.elasticsearch.snapshots.SnapshotId;
3537
import org.elasticsearch.snapshots.SnapshotInfo;
@@ -39,11 +41,13 @@
3941
import org.elasticsearch.transport.TransportService;
4042

4143
import java.util.ArrayList;
44+
import java.util.Collections;
4245
import java.util.HashMap;
4346
import java.util.HashSet;
4447
import java.util.List;
4548
import java.util.Map;
4649
import java.util.Set;
50+
import java.util.stream.Collectors;
4751

4852
/**
4953
* Transport Action for get snapshots operation
@@ -76,31 +80,35 @@ protected ClusterBlockException checkBlock(GetSnapshotsRequest request, ClusterS
7680
}
7781

7882
@Override
79-
protected void masterOperation(final GetSnapshotsRequest request, ClusterState state,
83+
protected void masterOperation(final GetSnapshotsRequest request, final ClusterState state,
8084
final ActionListener<GetSnapshotsResponse> listener) {
8185
try {
8286
final String repository = request.repository();
83-
List<SnapshotInfo> snapshotInfoBuilder = new ArrayList<>();
8487
final Map<String, SnapshotId> allSnapshotIds = new HashMap<>();
85-
final List<SnapshotId> currentSnapshotIds = new ArrayList<>();
86-
final RepositoryData repositoryData = snapshotsService.getRepositoryData(repository);
88+
final List<SnapshotInfo> currentSnapshots = new ArrayList<>();
8789
for (SnapshotInfo snapshotInfo : snapshotsService.currentSnapshots(repository)) {
8890
SnapshotId snapshotId = snapshotInfo.snapshotId();
8991
allSnapshotIds.put(snapshotId.getName(), snapshotId);
90-
currentSnapshotIds.add(snapshotId);
92+
currentSnapshots.add(snapshotInfo);
9193
}
94+
95+
final RepositoryData repositoryData;
9296
if (isCurrentSnapshotsOnly(request.snapshots()) == false) {
97+
repositoryData = snapshotsService.getRepositoryData(repository);
9398
for (SnapshotId snapshotId : repositoryData.getAllSnapshotIds()) {
9499
allSnapshotIds.put(snapshotId.getName(), snapshotId);
95100
}
101+
} else {
102+
repositoryData = null;
96103
}
104+
97105
final Set<SnapshotId> toResolve = new HashSet<>();
98106
if (isAllSnapshots(request.snapshots())) {
99107
toResolve.addAll(allSnapshotIds.values());
100108
} else {
101109
for (String snapshotOrPattern : request.snapshots()) {
102110
if (GetSnapshotsRequest.CURRENT_SNAPSHOT.equalsIgnoreCase(snapshotOrPattern)) {
103-
toResolve.addAll(currentSnapshotIds);
111+
toResolve.addAll(currentSnapshots.stream().map(SnapshotInfo::snapshotId).collect(Collectors.toList()));
104112
} else if (Regex.isSimpleMatchPattern(snapshotOrPattern) == false) {
105113
if (allSnapshotIds.containsKey(snapshotOrPattern)) {
106114
toResolve.add(allSnapshotIds.get(snapshotOrPattern));
@@ -121,9 +129,23 @@ protected void masterOperation(final GetSnapshotsRequest request, ClusterState s
121129
}
122130
}
123131

124-
snapshotInfoBuilder.addAll(snapshotsService.snapshots(
125-
repository, new ArrayList<>(toResolve), repositoryData.getIncompatibleSnapshotIds(), request.ignoreUnavailable()));
126-
listener.onResponse(new GetSnapshotsResponse(snapshotInfoBuilder));
132+
final List<SnapshotInfo> snapshotInfos;
133+
if (request.verbose()) {
134+
final Set<SnapshotId> incompatibleSnapshots = repositoryData != null ?
135+
new HashSet<>(repositoryData.getIncompatibleSnapshotIds()) : Collections.emptySet();
136+
snapshotInfos = snapshotsService.snapshots(repository, new ArrayList<>(toResolve),
137+
incompatibleSnapshots, request.ignoreUnavailable());
138+
} else {
139+
if (repositoryData != null) {
140+
// want non-current snapshots as well, which are found in the repository data
141+
snapshotInfos = buildSimpleSnapshotInfos(toResolve, repositoryData, currentSnapshots);
142+
} else {
143+
// only want current snapshots
144+
snapshotInfos = currentSnapshots.stream().map(SnapshotInfo::basic).collect(Collectors.toList());
145+
CollectionUtil.timSort(snapshotInfos);
146+
}
147+
}
148+
listener.onResponse(new GetSnapshotsResponse(snapshotInfos));
127149
} catch (Exception e) {
128150
listener.onFailure(e);
129151
}
@@ -136,4 +158,32 @@ private boolean isAllSnapshots(String[] snapshots) {
136158
private boolean isCurrentSnapshotsOnly(String[] snapshots) {
137159
return (snapshots.length == 1 && GetSnapshotsRequest.CURRENT_SNAPSHOT.equalsIgnoreCase(snapshots[0]));
138160
}
161+
162+
private List<SnapshotInfo> buildSimpleSnapshotInfos(final Set<SnapshotId> toResolve,
163+
final RepositoryData repositoryData,
164+
final List<SnapshotInfo> currentSnapshots) {
165+
List<SnapshotInfo> snapshotInfos = new ArrayList<>();
166+
for (SnapshotInfo snapshotInfo : currentSnapshots) {
167+
if (toResolve.remove(snapshotInfo.snapshotId())) {
168+
snapshotInfos.add(snapshotInfo.basic());
169+
}
170+
}
171+
Map<SnapshotId, List<String>> snapshotsToIndices = new HashMap<>();
172+
for (IndexId indexId : repositoryData.getIndices().values()) {
173+
for (SnapshotId snapshotId : repositoryData.getSnapshots(indexId)) {
174+
if (toResolve.contains(snapshotId)) {
175+
snapshotsToIndices.computeIfAbsent(snapshotId, (k) -> new ArrayList<>())
176+
.add(indexId.getName());
177+
}
178+
}
179+
}
180+
for (Map.Entry<SnapshotId, List<String>> entry : snapshotsToIndices.entrySet()) {
181+
final List<String> indices = entry.getValue();
182+
CollectionUtil.timSort(indices);
183+
final SnapshotId snapshotId = entry.getKey();
184+
snapshotInfos.add(new SnapshotInfo(snapshotId, indices, repositoryData.getSnapshotState(snapshotId)));
185+
}
186+
CollectionUtil.timSort(snapshotInfos);
187+
return Collections.unmodifiableList(snapshotInfos);
188+
}
139189
}

0 commit comments

Comments
 (0)