Skip to content

Commit 6a34532

Browse files
authored
Add ?storage arg to mount API (#68431)
This commit introduces a new flag, `?partial_local_copy`, indicating that the local copy of a searchable snapshot should be partial rather than complete, enabling the frozen tier functionality.
1 parent 9fb9309 commit 6a34532

File tree

18 files changed

+207
-47
lines changed

18 files changed

+207
-47
lines changed

docs/reference/searchable-snapshots/apis/mount-snapshot.asciidoc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ include::{es-repo-dir}/rest-api/common-parms.asciidoc[tag=master-timeout]
4949
(Optional, Boolean) If `true`, the request blocks until the operation is complete.
5050
Defaults to `false`.
5151

52+
`storage`::
53+
+
54+
experimental::[]
55+
+
56+
(Optional, string) Selects the kind of local storage used to accelerate
57+
searches of the mounted index. If `full_copy`, each node holding a shard of the
58+
searchable snapshot index makes a full copy of the shard to its local storage.
59+
If `shared_cache`, the shard uses the
60+
<<searchable-snapshots-shared-cache,shared cache>>. Defaults to `full_copy`.
61+
5262
[[searchable-snapshots-api-mount-request-body]]
5363
==== {api-request-body-title}
5464

docs/reference/searchable-snapshots/index.asciidoc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,13 @@ A snapshot of a {search-snap} index contains only a small amount of metadata
120120
which identifies its original index snapshot. It does not contain any data from
121121
the original index. The restore of a backup will fail to restore any
122122
{search-snap} indices whose original index snapshot is unavailable.
123+
124+
[discrete]
125+
[[searchable-snapshots-shared-cache]]
126+
=== Shared snapshot cache
127+
128+
experimental::[]
129+
130+
By default a {search-snap} copies the whole snapshot into the local cluster as
131+
described above. Nodes can also be configured to create a shared snapshot cache
132+
which is used to hold a copy of just the frequently-accessed parts of shards.

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/MountSnapshotStep.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,9 @@ void performDuringNoSnapshot(IndexMetadata indexMetadata, ClusterState currentCl
9090
new String[]{LifecycleSettings.LIFECYCLE_NAME},
9191
// we'll not wait for the snapshot to complete in this step as the async steps are executed from threads that shouldn't
9292
// perform expensive operations (ie. clusterStateProcessed)
93-
false);
93+
false,
94+
// restoring into the cold tier, so use a full local copy
95+
MountSearchableSnapshotRequest.Storage.FULL_COPY);
9496
getClient().execute(MountSearchableSnapshotAction.INSTANCE, mountSearchableSnapshotRequest,
9597
ActionListener.wrap(response -> {
9698
if (response.status() != RestStatus.OK && response.status() != RestStatus.ACCEPTED) {

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/searchablesnapshots/MountSearchableSnapshotRequest.java

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,17 @@
1414
import org.elasticsearch.common.Strings;
1515
import org.elasticsearch.common.io.stream.StreamInput;
1616
import org.elasticsearch.common.io.stream.StreamOutput;
17+
import org.elasticsearch.common.io.stream.Writeable;
1718
import org.elasticsearch.common.settings.Settings;
1819
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
1920
import org.elasticsearch.common.xcontent.ObjectParser;
2021
import org.elasticsearch.common.xcontent.XContentParser;
2122
import org.elasticsearch.rest.RestRequest;
23+
import org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshotsConstants;
2224

2325
import java.io.IOException;
2426
import java.util.Arrays;
27+
import java.util.Locale;
2528
import java.util.Objects;
2629
import java.util.stream.Collectors;
2730

@@ -37,12 +40,13 @@ public class MountSearchableSnapshotRequest extends MasterNodeRequest<MountSearc
3740
"mount_searchable_snapshot", true,
3841
(a, request) -> new MountSearchableSnapshotRequest(
3942
Objects.requireNonNullElse((String)a[1], (String)a[0]),
40-
request.param("repository"),
41-
request.param("snapshot"),
43+
Objects.requireNonNull(request.param("repository")),
44+
Objects.requireNonNull(request.param("snapshot")),
4245
(String)a[0],
4346
Objects.requireNonNullElse((Settings)a[2], Settings.EMPTY),
4447
Objects.requireNonNullElse((String[])a[3], Strings.EMPTY_ARRAY),
45-
request.paramAsBoolean("wait_for_completion", false)));
48+
request.paramAsBoolean("wait_for_completion", false),
49+
Storage.valueOf(request.param("storage", Storage.FULL_COPY.toString()).toUpperCase(Locale.ROOT))));
4650

4751
private static final ParseField INDEX_FIELD = new ParseField("index");
4852
private static final ParseField RENAMED_INDEX_FIELD = new ParseField("renamed_index");
@@ -65,19 +69,28 @@ public class MountSearchableSnapshotRequest extends MasterNodeRequest<MountSearc
6569
private final Settings indexSettings;
6670
private final String[] ignoredIndexSettings;
6771
private final boolean waitForCompletion;
72+
private final Storage storage;
6873

6974
/**
7075
* Constructs a new mount searchable snapshot request, restoring an index with the settings needed to make it a searchable snapshot.
7176
*/
72-
public MountSearchableSnapshotRequest(String mountedIndexName, String repositoryName, String snapshotName, String snapshotIndexName,
73-
Settings indexSettings, String[] ignoredIndexSettings, boolean waitForCompletion) {
77+
public MountSearchableSnapshotRequest(
78+
String mountedIndexName,
79+
String repositoryName,
80+
String snapshotName,
81+
String snapshotIndexName,
82+
Settings indexSettings,
83+
String[] ignoredIndexSettings,
84+
boolean waitForCompletion,
85+
Storage storage) {
7486
this.mountedIndexName = Objects.requireNonNull(mountedIndexName);
7587
this.repositoryName = Objects.requireNonNull(repositoryName);
7688
this.snapshotName = Objects.requireNonNull(snapshotName);
7789
this.snapshotIndexName = Objects.requireNonNull(snapshotIndexName);
7890
this.indexSettings = Objects.requireNonNull(indexSettings);
7991
this.ignoredIndexSettings = Objects.requireNonNull(ignoredIndexSettings);
8092
this.waitForCompletion = waitForCompletion;
93+
this.storage = storage;
8194
}
8295

8396
public MountSearchableSnapshotRequest(StreamInput in) throws IOException {
@@ -89,6 +102,11 @@ public MountSearchableSnapshotRequest(StreamInput in) throws IOException {
89102
this.indexSettings = readSettingsFromStream(in);
90103
this.ignoredIndexSettings = in.readStringArray();
91104
this.waitForCompletion = in.readBoolean();
105+
if (in.getVersion().onOrAfter(SearchableSnapshotsConstants.SHARED_CACHE_VERSION)) {
106+
this.storage = Storage.readFromStream(in);
107+
} else {
108+
this.storage = Storage.FULL_COPY;
109+
}
92110
}
93111

94112
@Override
@@ -101,6 +119,12 @@ public void writeTo(StreamOutput out) throws IOException {
101119
writeSettingsToStream(indexSettings, out);
102120
out.writeStringArray(ignoredIndexSettings);
103121
out.writeBoolean(waitForCompletion);
122+
if (out.getVersion().onOrAfter(SearchableSnapshotsConstants.SHARED_CACHE_VERSION)) {
123+
storage.writeTo(out);
124+
} else if (storage != Storage.FULL_COPY) {
125+
throw new UnsupportedOperationException(
126+
"storage type [" + storage + "] is not supported on version [" + out.getVersion() + "]");
127+
}
104128
}
105129

106130
@Override
@@ -162,6 +186,13 @@ public String[] ignoreIndexSettings() {
162186
return ignoredIndexSettings;
163187
}
164188

189+
/**
190+
* @return how nodes will use their local storage to accelerate searches of this snapshot
191+
*/
192+
public Storage storage() {
193+
return storage;
194+
}
195+
165196
@Override
166197
public String getDescription() {
167198
return "mount snapshot [" + repositoryName + ":" + snapshotName + ":" + snapshotIndexName + "] as [" + mountedIndexName + "]";
@@ -173,6 +204,7 @@ public boolean equals(Object o) {
173204
if (o == null || getClass() != o.getClass()) return false;
174205
MountSearchableSnapshotRequest that = (MountSearchableSnapshotRequest) o;
175206
return waitForCompletion == that.waitForCompletion &&
207+
storage == that.storage &&
176208
Objects.equals(mountedIndexName, that.mountedIndexName) &&
177209
Objects.equals(repositoryName, that.repositoryName) &&
178210
Objects.equals(snapshotName, that.snapshotName) &&
@@ -185,7 +217,7 @@ public boolean equals(Object o) {
185217
@Override
186218
public int hashCode() {
187219
int result = Objects.hash(mountedIndexName, repositoryName, snapshotName, snapshotIndexName, indexSettings, waitForCompletion,
188-
masterNodeTimeout);
220+
masterNodeTimeout, storage);
189221
result = 31 * result + Arrays.hashCode(ignoredIndexSettings);
190222
return result;
191223
}
@@ -194,4 +226,21 @@ public int hashCode() {
194226
public String toString() {
195227
return getDescription();
196228
}
229+
230+
/**
231+
* Enumerates the different ways that nodes can use their local storage to accelerate searches of a snapshot.
232+
*/
233+
public enum Storage implements Writeable {
234+
FULL_COPY,
235+
SHARED_CACHE;
236+
237+
public static Storage readFromStream(StreamInput in) throws IOException {
238+
return in.readEnum(Storage.class);
239+
}
240+
241+
@Override
242+
public void writeTo(StreamOutput out) throws IOException {
243+
out.writeEnum(this);
244+
}
245+
}
197246
}

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsConstants.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77
package org.elasticsearch.xpack.searchablesnapshots;
88

9+
import org.elasticsearch.Version;
910
import org.elasticsearch.common.settings.Settings;
1011

1112
import static org.elasticsearch.index.IndexModule.INDEX_STORE_TYPE_SETTING;
@@ -27,4 +28,6 @@ public static boolean isSearchableSnapshotStore(Settings indexSettings) {
2728

2829
public static final String SNAPSHOT_BLOB_CACHE_INDEX = ".snapshot-blob-cache";
2930

31+
public static final Version SHARED_CACHE_VERSION = Version.V_8_0_0;
32+
3033
}

x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/BaseSearchableSnapshotsIntegTestCase.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ protected void mountSnapshot(
126126
.put(restoredIndexSettings)
127127
.build(),
128128
Strings.EMPTY_ARRAY,
129-
true
129+
true,
130+
MountSearchableSnapshotRequest.Storage.FULL_COPY
130131
);
131132

132133
final RestoreSnapshotResponse restoreResponse = client().execute(MountSearchableSnapshotAction.INSTANCE, mountRequest).get();

x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/ClusterStateApplierOrderingTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ public void testRepositoriesServiceClusterStateApplierIsCalledBeforeIndicesClust
7373
indexName,
7474
indexSettingsBuilder.build(),
7575
Strings.EMPTY_ARRAY,
76-
true
76+
true,
77+
MountSearchableSnapshotRequest.Storage.FULL_COPY
7778
);
7879

7980
final RestoreSnapshotResponse restoreSnapshotResponse = client().execute(MountSearchableSnapshotAction.INSTANCE, req).get();

x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsCanMatchOnCoordinatorIntegTests.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,8 @@ public void testSearchableSnapshotShardsAreSkippedWithoutQueryingAnyNodeWhenThey
133133
indexOutsideSearchRange,
134134
restoredIndexSettings,
135135
Strings.EMPTY_ARRAY,
136-
false
136+
false,
137+
MountSearchableSnapshotRequest.Storage.FULL_COPY
137138
);
138139
client().execute(MountSearchableSnapshotAction.INSTANCE, mountRequest).actionGet();
139140

@@ -267,7 +268,8 @@ public void testQueryPhaseIsExecutedInAnAvailableNodeWhenAllShardsCanBeSkipped()
267268
indexOutsideSearchRange,
268269
restoredIndexSettings,
269270
Strings.EMPTY_ARRAY,
270-
false
271+
false,
272+
MountSearchableSnapshotRequest.Storage.FULL_COPY
271273
);
272274
client().execute(MountSearchableSnapshotAction.INSTANCE, mountRequest).actionGet();
273275
final int searchableSnapshotShardCount = indexOutsideSearchRangeShardCount;
@@ -375,7 +377,8 @@ public void testSearchableSnapshotShardsThatHaveMatchingDataAreNotSkippedOnTheCo
375377
indexWithinSearchRange,
376378
restoredIndexSettings,
377379
Strings.EMPTY_ARRAY,
378-
false
380+
false,
381+
MountSearchableSnapshotRequest.Storage.FULL_COPY
379382
);
380383
client().execute(MountSearchableSnapshotAction.INSTANCE, mountRequest).actionGet();
381384

x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsIntegTests.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,8 @@ public void testCreateAndRestoreSearchableSnapshot() throws Exception {
216216
indexName,
217217
indexSettingsBuilder.build(),
218218
Strings.EMPTY_ARRAY,
219-
true
219+
true,
220+
MountSearchableSnapshotRequest.Storage.FULL_COPY
220221
);
221222

222223
final RestoreSnapshotResponse restoreSnapshotResponse = client().execute(MountSearchableSnapshotAction.INSTANCE, req).get();
@@ -446,7 +447,7 @@ public void testCreateAndRestorePartialSearchableSnapshot() throws Exception {
446447
assertAcked(client().admin().indices().prepareClose(indexName));
447448
}
448449

449-
logger.info("--> restoring partial index [{}] with cache enabled");
450+
logger.info("--> restoring partial index [{}] with cache enabled", restoredIndexName);
450451

451452
Settings.Builder indexSettingsBuilder = Settings.builder()
452453
.put(SearchableSnapshots.SNAPSHOT_CACHE_ENABLED_SETTING.getKey(), true)
@@ -478,7 +479,6 @@ public void testCreateAndRestorePartialSearchableSnapshot() throws Exception {
478479
} else {
479480
expectedDataTiersPreference = DATA_TIERS_PREFERENCE;
480481
}
481-
indexSettingsBuilder.put(SearchableSnapshots.SNAPSHOT_PARTIAL_SETTING.getKey(), true);
482482

483483
final MountSearchableSnapshotRequest req = new MountSearchableSnapshotRequest(
484484
restoredIndexName,
@@ -487,7 +487,8 @@ public void testCreateAndRestorePartialSearchableSnapshot() throws Exception {
487487
indexName,
488488
indexSettingsBuilder.build(),
489489
Strings.EMPTY_ARRAY,
490-
true
490+
true,
491+
MountSearchableSnapshotRequest.Storage.SHARED_CACHE
491492
);
492493

493494
final RestoreSnapshotResponse restoreSnapshotResponse = client().execute(MountSearchableSnapshotAction.INSTANCE, req).get();
@@ -728,7 +729,8 @@ public void testCanMountSnapshotTakenWhileConcurrentlyIndexing() throws Exceptio
728729
indexName,
729730
indexSettingsBuilder.build(),
730731
Strings.EMPTY_ARRAY,
731-
true
732+
true,
733+
MountSearchableSnapshotRequest.Storage.FULL_COPY
732734
);
733735

734736
final RestoreSnapshotResponse restoreSnapshotResponse = client().execute(MountSearchableSnapshotAction.INSTANCE, req).get();
@@ -783,7 +785,8 @@ public void testMaxRestoreBytesPerSecIsUsed() throws Exception {
783785
indexName,
784786
Settings.builder().put(IndexSettings.INDEX_CHECK_ON_STARTUP.getKey(), Boolean.FALSE.toString()).build(),
785787
Strings.EMPTY_ARRAY,
786-
true
788+
true,
789+
MountSearchableSnapshotRequest.Storage.FULL_COPY
787790
);
788791

789792
final RestoreSnapshotResponse restore = client().execute(MountSearchableSnapshotAction.INSTANCE, mount).get();
@@ -862,7 +865,8 @@ public void testMountedSnapshotHasNoReplicasByDefault() throws Exception {
862865
indexName,
863866
indexSettingsBuilder.build(),
864867
Strings.EMPTY_ARRAY,
865-
true
868+
true,
869+
MountSearchableSnapshotRequest.Storage.FULL_COPY
866870
);
867871

868872
final RestoreSnapshotResponse restoreSnapshotResponse = client().execute(MountSearchableSnapshotAction.INSTANCE, req).get();
@@ -896,7 +900,8 @@ public void testMountedSnapshotHasNoReplicasByDefault() throws Exception {
896900
indexName,
897901
indexSettingsBuilder.build(),
898902
Strings.EMPTY_ARRAY,
899-
true
903+
true,
904+
MountSearchableSnapshotRequest.Storage.FULL_COPY
900905
);
901906

902907
final RestoreSnapshotResponse restoreSnapshotResponse = client().execute(MountSearchableSnapshotAction.INSTANCE, req).get();
@@ -929,7 +934,8 @@ public void testMountedSnapshotHasNoReplicasByDefault() throws Exception {
929934
indexName,
930935
indexSettingsBuilder.build(),
931936
Strings.EMPTY_ARRAY,
932-
true
937+
true,
938+
MountSearchableSnapshotRequest.Storage.FULL_COPY
933939
);
934940

935941
final RestoreSnapshotResponse restoreSnapshotResponse = client().execute(MountSearchableSnapshotAction.INSTANCE, req).get();

x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsLicenseIntegTests.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ public void createAndMountSearchableSnapshot() throws Exception {
7777
indexName,
7878
indexSettingsBuilder.build(),
7979
Strings.EMPTY_ARRAY,
80-
true
80+
true,
81+
randomFrom(MountSearchableSnapshotRequest.Storage.values())
8182
);
8283

8384
final RestoreSnapshotResponse restoreSnapshotResponse = client().execute(MountSearchableSnapshotAction.INSTANCE, req).get();
@@ -96,7 +97,8 @@ public void testMountRequiresLicense() {
9697
indexName,
9798
Settings.EMPTY,
9899
Strings.EMPTY_ARRAY,
99-
randomBoolean()
100+
randomBoolean(),
101+
randomFrom(MountSearchableSnapshotRequest.Storage.values())
100102
);
101103

102104
final ActionFuture<RestoreSnapshotResponse> future = client().execute(MountSearchableSnapshotAction.INSTANCE, req);

x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsPrewarmingIntegTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,8 @@ public void testConcurrentPrewarming() throws Exception {
224224
indexName,
225225
restoredIndexSettings,
226226
Strings.EMPTY_ARRAY,
227-
true
227+
true,
228+
MountSearchableSnapshotRequest.Storage.FULL_COPY
228229
)
229230
).get();
230231

x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsSettingValidationIntegTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ public void createAndMountSearchableSnapshot() throws Exception {
4444
indexName,
4545
indexSettingsBuilder.build(),
4646
Strings.EMPTY_ARRAY,
47-
true
47+
true,
48+
randomFrom(MountSearchableSnapshotRequest.Storage.values())
4849
);
4950

5051
final RestoreSnapshotResponse restoreSnapshotResponse = client().execute(MountSearchableSnapshotAction.INSTANCE, req).get();

x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsSystemIndicesIntegTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ private void executeTest(final String indexName, final Client client) throws Exc
7272
indexName,
7373
Settings.builder().put(IndexMetadata.SETTING_INDEX_HIDDEN, randomBoolean()).build(),
7474
Strings.EMPTY_ARRAY,
75-
true
75+
true,
76+
randomFrom(MountSearchableSnapshotRequest.Storage.values())
7677
);
7778

7879
final ElasticsearchException exception = expectThrows(

x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsUuidValidationIntegTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ public void testMountFailsIfSnapshotChanged() throws Exception {
101101
indexName,
102102
Settings.EMPTY,
103103
Strings.EMPTY_ARRAY,
104-
true
104+
true,
105+
randomFrom(MountSearchableSnapshotRequest.Storage.values())
105106
);
106107

107108
final ActionFuture<RestoreSnapshotResponse> responseFuture = client().execute(MountSearchableSnapshotAction.INSTANCE, req);

0 commit comments

Comments
 (0)