Skip to content

Commit e832d87

Browse files
SNAPSHOTS: Allow Parallel Restore Operations (#36397) (#36659)
* Enable parallel restore operations * Add uuid to restore in progress entries to uniquely identify them * Adjust restore in progress entries to be a map in cluster state * Added tests for: * Parallel restore from two different snapshots * Parallel restore from a single snapshot to different indices to test uuid identifiers are correctly used by `RestoreService` and routing allocator * Parallel restore with waiting for completion to test transport actions correctly use uuid identifiers
1 parent 436336e commit e832d87

File tree

24 files changed

+376
-141
lines changed

24 files changed

+376
-141
lines changed

docs/reference/indices/recovery.asciidoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ Response:
9090
"repository" : "my_repository",
9191
"snapshot" : "my_snapshot",
9292
"index" : "index1",
93-
"version" : "{version}"
93+
"version" : "{version}",
94+
"restoreUUID": "PDh1ZAOaRbiGIVtCvZOMww"
9495
},
9596
"target" : {
9697
"id" : "ryqJ5lO5S4-lSFbGntkEkg",

server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/TransportRestoreSnapshotAction.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,13 @@ protected void masterOperation(final RestoreSnapshotRequest request, final Clust
9393
public void onResponse(RestoreCompletionResponse restoreCompletionResponse) {
9494
if (restoreCompletionResponse.getRestoreInfo() == null && request.waitForCompletion()) {
9595
final Snapshot snapshot = restoreCompletionResponse.getSnapshot();
96+
String uuid = restoreCompletionResponse.getUuid();
9697

9798
ClusterStateListener clusterStateListener = new ClusterStateListener() {
9899
@Override
99100
public void clusterChanged(ClusterChangedEvent changedEvent) {
100-
final RestoreInProgress.Entry prevEntry = restoreInProgress(changedEvent.previousState(), snapshot);
101-
final RestoreInProgress.Entry newEntry = restoreInProgress(changedEvent.state(), snapshot);
101+
final RestoreInProgress.Entry prevEntry = restoreInProgress(changedEvent.previousState(), uuid);
102+
final RestoreInProgress.Entry newEntry = restoreInProgress(changedEvent.state(), uuid);
102103
if (prevEntry == null) {
103104
// When there is a master failure after a restore has been started, this listener might not be registered
104105
// on the current master and as such it might miss some intermediary cluster states due to batching.

server/src/main/java/org/elasticsearch/cluster/ClusterModule.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ public ClusterModule(Settings settings, ClusterService clusterService, List<Clus
118118
public static Map<String, Supplier<ClusterState.Custom>> getClusterStateCustomSuppliers(List<ClusterPlugin> clusterPlugins) {
119119
final Map<String, Supplier<ClusterState.Custom>> customSupplier = new HashMap<>();
120120
customSupplier.put(SnapshotDeletionsInProgress.TYPE, SnapshotDeletionsInProgress::new);
121-
customSupplier.put(RestoreInProgress.TYPE, RestoreInProgress::new);
121+
customSupplier.put(RestoreInProgress.TYPE, () -> new RestoreInProgress.Builder().build());
122122
customSupplier.put(SnapshotsInProgress.TYPE, SnapshotsInProgress::new);
123123
for (ClusterPlugin plugin : clusterPlugins) {
124124
Map<String, Supplier<ClusterState.Custom>> initialCustomSupplier = plugin.getInitialClusterStateCustomSupplier();

server/src/main/java/org/elasticsearch/cluster/RestoreInProgress.java

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

2020
package org.elasticsearch.cluster;
2121

22+
import com.carrotsearch.hppc.cursors.ObjectCursor;
2223
import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
2324
import org.elasticsearch.Version;
2425
import org.elasticsearch.cluster.ClusterState.Custom;
@@ -32,36 +33,33 @@
3233

3334
import java.io.IOException;
3435
import java.util.ArrayList;
35-
import java.util.Arrays;
3636
import java.util.Collections;
37+
import java.util.Iterator;
3738
import java.util.List;
3839
import java.util.Objects;
40+
import java.util.UUID;
3941

4042
/**
4143
* Meta data about restore processes that are currently executing
4244
*/
43-
public class RestoreInProgress extends AbstractNamedDiffable<Custom> implements Custom {
45+
public class RestoreInProgress extends AbstractNamedDiffable<Custom> implements Custom, Iterable<RestoreInProgress.Entry> {
46+
47+
/**
48+
* Fallback UUID used for restore operations that were started before v7.0 and don't have a uuid in the cluster state.
49+
*/
50+
public static final String BWC_UUID = new UUID(0, 0).toString();
4451

4552
public static final String TYPE = "restore";
4653

47-
private final List<Entry> entries;
54+
private final ImmutableOpenMap<String, Entry> entries;
4855

4956
/**
5057
* Constructs new restore metadata
5158
*
52-
* @param entries list of currently running restore processes
59+
* @param entries map of currently running restore processes keyed by their restore uuid
5360
*/
54-
public RestoreInProgress(Entry... entries) {
55-
this.entries = Arrays.asList(entries);
56-
}
57-
58-
/**
59-
* Returns list of currently running restore processes
60-
*
61-
* @return list of currently running restore processes
62-
*/
63-
public List<Entry> entries() {
64-
return this.entries;
61+
private RestoreInProgress(ImmutableOpenMap<String, Entry> entries) {
62+
this.entries = entries;
6563
}
6664

6765
@Override
@@ -83,20 +81,48 @@ public int hashCode() {
8381

8482
@Override
8583
public String toString() {
86-
StringBuilder builder = new StringBuilder("RestoreInProgress[");
87-
for (int i = 0; i < entries.size(); i++) {
88-
builder.append(entries.get(i).snapshot().getSnapshotId().getName());
89-
if (i + 1 < entries.size()) {
90-
builder.append(",");
91-
}
84+
return new StringBuilder("RestoreInProgress[").append(entries).append("]").toString();
85+
}
86+
87+
public Entry get(String restoreUUID) {
88+
return entries.get(restoreUUID);
89+
}
90+
91+
public boolean isEmpty() {
92+
return entries.isEmpty();
93+
}
94+
95+
@Override
96+
public Iterator<Entry> iterator() {
97+
return entries.valuesIt();
98+
}
99+
100+
public static final class Builder {
101+
102+
private final ImmutableOpenMap.Builder<String, Entry> entries = ImmutableOpenMap.builder();
103+
104+
public Builder() {
105+
}
106+
107+
public Builder(RestoreInProgress restoreInProgress) {
108+
entries.putAll(restoreInProgress.entries);
109+
}
110+
111+
public Builder add(Entry entry) {
112+
entries.put(entry.uuid, entry);
113+
return this;
114+
}
115+
116+
public RestoreInProgress build() {
117+
return new RestoreInProgress(entries.build());
92118
}
93-
return builder.append("]").toString();
94119
}
95120

96121
/**
97122
* Restore metadata
98123
*/
99124
public static class Entry {
125+
private final String uuid;
100126
private final State state;
101127
private final Snapshot snapshot;
102128
private final ImmutableOpenMap<ShardId, ShardRestoreStatus> shards;
@@ -105,12 +131,14 @@ public static class Entry {
105131
/**
106132
* Creates new restore metadata
107133
*
134+
* @param uuid uuid of the restore
108135
* @param snapshot snapshot
109136
* @param state current state of the restore process
110137
* @param indices list of indices being restored
111138
* @param shards map of shards being restored to their current restore status
112139
*/
113-
public Entry(Snapshot snapshot, State state, List<String> indices, ImmutableOpenMap<ShardId, ShardRestoreStatus> shards) {
140+
public Entry(String uuid, Snapshot snapshot, State state, List<String> indices,
141+
ImmutableOpenMap<ShardId, ShardRestoreStatus> shards) {
114142
this.snapshot = Objects.requireNonNull(snapshot);
115143
this.state = Objects.requireNonNull(state);
116144
this.indices = Objects.requireNonNull(indices);
@@ -119,6 +147,15 @@ public Entry(Snapshot snapshot, State state, List<String> indices, ImmutableOpen
119147
} else {
120148
this.shards = shards;
121149
}
150+
this.uuid = Objects.requireNonNull(uuid);
151+
}
152+
153+
/**
154+
* Returns restore uuid
155+
* @return restore uuid
156+
*/
157+
public String uuid() {
158+
return uuid;
122159
}
123160

124161
/**
@@ -166,15 +203,16 @@ public boolean equals(Object o) {
166203
return false;
167204
}
168205
@SuppressWarnings("unchecked") Entry entry = (Entry) o;
169-
return snapshot.equals(entry.snapshot) &&
206+
return uuid.equals(entry.uuid) &&
207+
snapshot.equals(entry.snapshot) &&
170208
state == entry.state &&
171209
indices.equals(entry.indices) &&
172210
shards.equals(entry.shards);
173211
}
174212

175213
@Override
176214
public int hashCode() {
177-
return Objects.hash(snapshot, state, indices, shards);
215+
return Objects.hash(uuid, snapshot, state, indices, shards);
178216
}
179217
}
180218

@@ -393,8 +431,15 @@ public static NamedDiff<Custom> readDiffFrom(StreamInput in) throws IOException
393431
}
394432

395433
public RestoreInProgress(StreamInput in) throws IOException {
396-
Entry[] entries = new Entry[in.readVInt()];
397-
for (int i = 0; i < entries.length; i++) {
434+
int count = in.readVInt();
435+
final ImmutableOpenMap.Builder<String, Entry> entriesBuilder = ImmutableOpenMap.builder(count);
436+
for (int i = 0; i < count; i++) {
437+
final String uuid;
438+
if (in.getVersion().onOrAfter(Version.V_6_6_0)) {
439+
uuid = in.readString();
440+
} else {
441+
uuid = BWC_UUID;
442+
}
398443
Snapshot snapshot = new Snapshot(in);
399444
State state = State.fromValue(in.readByte());
400445
int indices = in.readVInt();
@@ -409,9 +454,9 @@ public RestoreInProgress(StreamInput in) throws IOException {
409454
ShardRestoreStatus shardState = ShardRestoreStatus.readShardRestoreStatus(in);
410455
builder.put(shardId, shardState);
411456
}
412-
entries[i] = new Entry(snapshot, state, Collections.unmodifiableList(indexBuilder), builder.build());
457+
entriesBuilder.put(uuid, new Entry(uuid, snapshot, state, Collections.unmodifiableList(indexBuilder), builder.build()));
413458
}
414-
this.entries = Arrays.asList(entries);
459+
this.entries = entriesBuilder.build();
415460
}
416461

417462
/**
@@ -420,7 +465,11 @@ public RestoreInProgress(StreamInput in) throws IOException {
420465
@Override
421466
public void writeTo(StreamOutput out) throws IOException {
422467
out.writeVInt(entries.size());
423-
for (Entry entry : entries) {
468+
for (ObjectCursor<Entry> v : entries.values()) {
469+
Entry entry = v.value;
470+
if (out.getVersion().onOrAfter(Version.V_6_6_0)) {
471+
out.writeString(entry.uuid);
472+
}
424473
entry.snapshot().writeTo(out);
425474
out.writeByte(entry.state().value());
426475
out.writeVInt(entry.indices().size());
@@ -441,8 +490,8 @@ public void writeTo(StreamOutput out) throws IOException {
441490
@Override
442491
public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
443492
builder.startArray("snapshots");
444-
for (Entry entry : entries) {
445-
toXContent(entry, builder, params);
493+
for (ObjectCursor<Entry> entry : entries.values()) {
494+
toXContent(entry.value, builder, params);
446495
}
447496
builder.endArray();
448497
return builder;

server/src/main/java/org/elasticsearch/cluster/routing/RecoverySource.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.elasticsearch.cluster.routing;
2121

2222
import org.elasticsearch.Version;
23+
import org.elasticsearch.cluster.RestoreInProgress;
2324
import org.elasticsearch.common.io.stream.StreamInput;
2425
import org.elasticsearch.common.io.stream.StreamOutput;
2526
import org.elasticsearch.common.io.stream.Writeable;
@@ -208,22 +209,33 @@ public String toString() {
208209
* recovery from a snapshot
209210
*/
210211
public static class SnapshotRecoverySource extends RecoverySource {
212+
private final String restoreUUID;
211213
private final Snapshot snapshot;
212214
private final String index;
213215
private final Version version;
214216

215-
public SnapshotRecoverySource(Snapshot snapshot, Version version, String index) {
217+
public SnapshotRecoverySource(String restoreUUID, Snapshot snapshot, Version version, String index) {
218+
this.restoreUUID = restoreUUID;
216219
this.snapshot = Objects.requireNonNull(snapshot);
217220
this.version = Objects.requireNonNull(version);
218221
this.index = Objects.requireNonNull(index);
219222
}
220223

221224
SnapshotRecoverySource(StreamInput in) throws IOException {
225+
if (in.getVersion().onOrAfter(Version.V_6_6_0)) {
226+
restoreUUID = in.readString();
227+
} else {
228+
restoreUUID = RestoreInProgress.BWC_UUID;
229+
}
222230
snapshot = new Snapshot(in);
223231
version = Version.readVersion(in);
224232
index = in.readString();
225233
}
226234

235+
public String restoreUUID() {
236+
return restoreUUID;
237+
}
238+
227239
public Snapshot snapshot() {
228240
return snapshot;
229241
}
@@ -238,6 +250,9 @@ public Version version() {
238250

239251
@Override
240252
protected void writeAdditionalFields(StreamOutput out) throws IOException {
253+
if (out.getVersion().onOrAfter(Version.V_6_6_0)) {
254+
out.writeString(restoreUUID);
255+
}
241256
snapshot.writeTo(out);
242257
Version.writeVersion(version, out);
243258
out.writeString(index);
@@ -253,12 +268,13 @@ public void addAdditionalFields(XContentBuilder builder, ToXContent.Params param
253268
builder.field("repository", snapshot.getRepository())
254269
.field("snapshot", snapshot.getSnapshotId().getName())
255270
.field("version", version.toString())
256-
.field("index", index);
271+
.field("index", index)
272+
.field("restoreUUID", restoreUUID);
257273
}
258274

259275
@Override
260276
public String toString() {
261-
return "snapshot recovery from " + snapshot.toString();
277+
return "snapshot recovery [" + restoreUUID + "] from " + snapshot;
262278
}
263279

264280
@Override
@@ -271,12 +287,13 @@ public boolean equals(Object o) {
271287
}
272288

273289
@SuppressWarnings("unchecked") SnapshotRecoverySource that = (SnapshotRecoverySource) o;
274-
return snapshot.equals(that.snapshot) && index.equals(that.index) && version.equals(that.version);
290+
return restoreUUID.equals(that.restoreUUID) && snapshot.equals(that.snapshot)
291+
&& index.equals(that.index) && version.equals(that.version);
275292
}
276293

277294
@Override
278295
public int hashCode() {
279-
return Objects.hash(snapshot, index, version);
296+
return Objects.hash(restoreUUID, snapshot, index, version);
280297
}
281298

282299
}

server/src/main/java/org/elasticsearch/cluster/routing/allocation/decider/RestoreInProgressAllocationDecider.java

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import org.elasticsearch.cluster.routing.RoutingNode;
2525
import org.elasticsearch.cluster.routing.ShardRouting;
2626
import org.elasticsearch.cluster.routing.allocation.RoutingAllocation;
27-
import org.elasticsearch.snapshots.Snapshot;
2827

2928
/**
3029
* This {@link AllocationDecider} prevents shards that have failed to be
@@ -46,25 +45,24 @@ public Decision canAllocate(final ShardRouting shardRouting, final RoutingAlloca
4645
return allocation.decision(Decision.YES, NAME, "ignored as shard is not being recovered from a snapshot");
4746
}
4847

49-
final Snapshot snapshot = ((RecoverySource.SnapshotRecoverySource) recoverySource).snapshot();
48+
RecoverySource.SnapshotRecoverySource source = (RecoverySource.SnapshotRecoverySource) recoverySource;
5049
final RestoreInProgress restoresInProgress = allocation.custom(RestoreInProgress.TYPE);
5150

5251
if (restoresInProgress != null) {
53-
for (RestoreInProgress.Entry restoreInProgress : restoresInProgress.entries()) {
54-
if (restoreInProgress.snapshot().equals(snapshot)) {
55-
RestoreInProgress.ShardRestoreStatus shardRestoreStatus = restoreInProgress.shards().get(shardRouting.shardId());
56-
if (shardRestoreStatus != null && shardRestoreStatus.state().completed() == false) {
57-
assert shardRestoreStatus.state() != RestoreInProgress.State.SUCCESS : "expected shard [" + shardRouting
58-
+ "] to be in initializing state but got [" + shardRestoreStatus.state() + "]";
59-
return allocation.decision(Decision.YES, NAME, "shard is currently being restored");
60-
}
61-
break;
52+
RestoreInProgress.Entry restoreInProgress = restoresInProgress.get(source.restoreUUID());
53+
if (restoreInProgress != null) {
54+
RestoreInProgress.ShardRestoreStatus shardRestoreStatus = restoreInProgress.shards().get(shardRouting.shardId());
55+
if (shardRestoreStatus != null && shardRestoreStatus.state().completed() == false) {
56+
assert shardRestoreStatus.state() != RestoreInProgress.State.SUCCESS : "expected shard [" + shardRouting
57+
+ "] to be in initializing state but got [" + shardRestoreStatus.state() + "]";
58+
return allocation.decision(Decision.YES, NAME, "shard is currently being restored");
6259
}
6360
}
6461
}
6562
return allocation.decision(Decision.NO, NAME, "shard has failed to be restored from the snapshot [%s] because of [%s] - " +
6663
"manually close or delete the index [%s] in order to retry to restore the snapshot again or use the reroute API to force the " +
67-
"allocation of an empty primary shard", snapshot, shardRouting.unassignedInfo().getDetails(), shardRouting.getIndexName());
64+
"allocation of an empty primary shard",
65+
source.snapshot(), shardRouting.unassignedInfo().getDetails(), shardRouting.getIndexName());
6866
}
6967

7068
@Override

0 commit comments

Comments
 (0)