Skip to content

Commit e9bef38

Browse files
authored
[7.x] Add support for partial searchable snapshots to ILM (#68714) (#68762)
This commit adds support for the recently introduced partial searchable snapshot (#68509) to ILM. Searchable snapshot ILM actions may now be specified with a `storage` option, specifying either `full_copy` or `shared_cache` (similar to the "mount" API) to mount either a full or partial searchable snapshot: `json PUT _ilm/policy/my_policy { "policy": { "phases": { "cold": { "actions": { "searchable_snapshot" : { "snapshot_repository" : "backing_repo", "storage": "shared_cache" } } } } } } ` Internally, If more than one searchable snapshot action is specified (for example, a full searchable snapshot in the "cold" phase and a partial searchable snapshot in the "frozen" phase) ILM will re-use the existing snapshot when doing the second mount since a second snapshot is not required. Currently this is allowed for actions that use the same repository, however, multiple `searchable_snapshot` actions for the same index that use different repositories is not allowed (the ERROR state is entered). We plan to allow this in the future in subsequent work. If the `storage` option is not specified in the `searchable_snapshot` action, the mount type defaults to "shared_cache" in the frozen phase and "full_copy" in all other phases. Relates to #68605
1 parent a49b0c8 commit e9bef38

File tree

18 files changed

+679
-74
lines changed

18 files changed

+679
-74
lines changed

docs/reference/ilm/actions/ilm-searchable-snapshot.asciidoc

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ the shards are relocating, in which case they will not be merged.
5454
The `searchable_snapshot` action will continue executing even if not all shards
5555
are force merged.
5656

57+
`storage`::
58+
(Optional, string)
59+
Specifies the type of snapshot that should be mounted for a searchable snapshot. This corresponds to
60+
the <<searchable-snapshots-api-mount-query-params, `storage` option when mounting a snapshot>>.
61+
Defaults to `full_copy` in non-frozen phases, or `shared_cache` in the frozen phase.
62+
5763
[[ilm-searchable-snapshot-ex]]
5864
==== Examples
5965
[source,console]
@@ -65,7 +71,8 @@ PUT _ilm/policy/my_policy
6571
"cold": {
6672
"actions": {
6773
"searchable_snapshot" : {
68-
"snapshot_repository" : "backing_repo"
74+
"snapshot_repository" : "backing_repo",
75+
"storage": "shared_cache"
6976
}
7077
}
7178
}

server/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,18 @@ public <E extends Enum<E>> E readEnum(Class<E> enumClass) throws IOException {
12631263
return readEnum(enumClass, enumClass.getEnumConstants());
12641264
}
12651265

1266+
/**
1267+
* Reads an optional enum with type E that was serialized based on the value of its ordinal
1268+
*/
1269+
@Nullable
1270+
public <E extends Enum<E>> E readOptionalEnum(Class<E> enumClass) throws IOException {
1271+
if (readBoolean()) {
1272+
return readEnum(enumClass, enumClass.getEnumConstants());
1273+
} else {
1274+
return null;
1275+
}
1276+
}
1277+
12661278
private <E extends Enum<E>> E readEnum(Class<E> enumClass, E[] values) throws IOException {
12671279
int ordinal = readVInt();
12681280
if (ordinal < 0 || ordinal >= values.length) {

server/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1245,6 +1245,18 @@ public <E extends Enum<E>> void writeEnum(E enumValue) throws IOException {
12451245
writeVInt(enumValue.ordinal());
12461246
}
12471247

1248+
/**
1249+
* Writes an optional enum with type E based on its ordinal value
1250+
*/
1251+
public <E extends Enum<E>> void writeOptionalEnum(@Nullable E enumValue) throws IOException {
1252+
if (enumValue == null) {
1253+
writeBoolean(false);
1254+
} else {
1255+
writeBoolean(true);
1256+
writeVInt(enumValue.ordinal());
1257+
}
1258+
}
1259+
12481260
/**
12491261
* Writes an EnumSet with type E that by serialized it based on it's ordinal value
12501262
*/

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ public ClusterState performAction(Index index, ClusterState clusterState) {
8585
}
8686
relevantTargetCustomData.setSnapshotRepository(lifecycleState.getSnapshotRepository());
8787
relevantTargetCustomData.setSnapshotName(lifecycleState.getSnapshotName());
88+
relevantTargetCustomData.setSnapshotIndexName(lifecycleState.getSnapshotIndexName());
8889

8990
Metadata.Builder newMetadata = Metadata.builder(clusterState.getMetadata())
9091
.put(IndexMetadata.builder(targetIndexMetadata)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ public ClusterState performAction(Index index, ClusterState clusterState) {
7979
}
8080
newCustomData.setSnapshotName(snapshotName);
8181
newCustomData.setSnapshotRepository(snapshotRepository);
82+
newCustomData.setSnapshotIndexName(index.getName());
8283

8384
IndexMetadata.Builder indexMetadataBuilder = IndexMetadata.builder(indexMetaData);
8485
indexMetadataBuilder.putCustom(ILM_CUSTOM_METADATA_KEY, newCustomData.build().asMap());

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

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@ public class LifecycleExecutionState {
3737
private static final String FAILED_STEP_RETRY_COUNT = "failed_step_retry_count";
3838
private static final String STEP_INFO = "step_info";
3939
private static final String PHASE_DEFINITION = "phase_definition";
40-
private static final String SNAPSHOT_NAME ="snapshot_name";
41-
private static final String SNAPSHOT_REPOSITORY ="snapshot_repository";
40+
private static final String SNAPSHOT_NAME = "snapshot_name";
41+
private static final String SNAPSHOT_REPOSITORY = "snapshot_repository";
42+
private static final String SNAPSHOT_INDEX_NAME = "snapshot_index_name";
4243

4344
private final String phase;
4445
private final String action;
@@ -54,10 +55,12 @@ public class LifecycleExecutionState {
5455
private final Long stepTime;
5556
private final String snapshotName;
5657
private final String snapshotRepository;
58+
private final String snapshotIndexName;
5759

5860
private LifecycleExecutionState(String phase, String action, String step, String failedStep, Boolean isAutoRetryableError,
5961
Integer failedStepRetryCount, String stepInfo, String phaseDefinition, Long lifecycleDate,
60-
Long phaseTime, Long actionTime, Long stepTime, String snapshotRepository, String snapshotName) {
62+
Long phaseTime, Long actionTime, Long stepTime, String snapshotRepository, String snapshotName,
63+
String snapshotIndexName) {
6164
this.phase = phase;
6265
this.action = action;
6366
this.step = step;
@@ -72,6 +75,7 @@ private LifecycleExecutionState(String phase, String action, String step, String
7275
this.stepTime = stepTime;
7376
this.snapshotRepository = snapshotRepository;
7477
this.snapshotName = snapshotName;
78+
this.snapshotIndexName = snapshotIndexName;
7579
}
7680

7781
/**
@@ -131,6 +135,7 @@ public static Builder builder(LifecycleExecutionState state) {
131135
.setActionTime(state.actionTime)
132136
.setSnapshotRepository(state.snapshotRepository)
133137
.setSnapshotName(state.snapshotName)
138+
.setSnapshotIndexName(state.snapshotIndexName)
134139
.setStepTime(state.stepTime);
135140
}
136141

@@ -198,6 +203,9 @@ static LifecycleExecutionState fromCustomMetadata(Map<String, String> customData
198203
e, STEP_TIME, customData.get(STEP_TIME));
199204
}
200205
}
206+
if (customData.containsKey(SNAPSHOT_INDEX_NAME)) {
207+
builder.setSnapshotIndexName(customData.get(SNAPSHOT_INDEX_NAME));
208+
}
201209
return builder.build();
202210
}
203211

@@ -250,6 +258,9 @@ public Map<String, String> asMap() {
250258
if (snapshotName != null) {
251259
result.put(SNAPSHOT_NAME, snapshotName);
252260
}
261+
if (snapshotIndexName != null) {
262+
result.put(SNAPSHOT_INDEX_NAME, snapshotIndexName);
263+
}
253264
return Collections.unmodifiableMap(result);
254265
}
255266

@@ -309,6 +320,10 @@ public String getSnapshotRepository() {
309320
return snapshotRepository;
310321
}
311322

323+
public String getSnapshotIndexName() {
324+
return snapshotIndexName;
325+
}
326+
312327
@Override
313328
public boolean equals(Object o) {
314329
if (this == o) return true;
@@ -327,14 +342,15 @@ public boolean equals(Object o) {
327342
Objects.equals(getStepInfo(), that.getStepInfo()) &&
328343
Objects.equals(getSnapshotRepository(), that.getSnapshotRepository()) &&
329344
Objects.equals(getSnapshotName(), that.getSnapshotName()) &&
345+
Objects.equals(getSnapshotIndexName(), that.getSnapshotIndexName()) &&
330346
Objects.equals(getPhaseDefinition(), that.getPhaseDefinition());
331347
}
332348

333349
@Override
334350
public int hashCode() {
335351
return Objects.hash(getPhase(), getAction(), getStep(), getFailedStep(), isAutoRetryableError(), getFailedStepRetryCount(),
336352
getStepInfo(), getPhaseDefinition(), getLifecycleDate(), getPhaseTime(), getActionTime(), getStepTime(),
337-
getSnapshotRepository(), getSnapshotName());
353+
getSnapshotRepository(), getSnapshotName(), getSnapshotIndexName());
338354
}
339355

340356
@Override
@@ -357,6 +373,7 @@ public static class Builder {
357373
private Integer failedStepRetryCount;
358374
private String snapshotName;
359375
private String snapshotRepository;
376+
private String snapshotIndexName;
360377

361378
public Builder setPhase(String phase) {
362379
this.phase = phase;
@@ -428,9 +445,14 @@ public Builder setSnapshotName(String snapshotName) {
428445
return this;
429446
}
430447

448+
public Builder setSnapshotIndexName(String snapshotIndexName) {
449+
this.snapshotIndexName = snapshotIndexName;
450+
return this;
451+
}
452+
431453
public LifecycleExecutionState build() {
432454
return new LifecycleExecutionState(phase, action, step, failedStep, isAutoRetryableError, failedStepRetryCount, stepInfo,
433-
phaseDefinition, indexCreationDate, phaseTime, actionTime, stepTime, snapshotRepository, snapshotName);
455+
phaseDefinition, indexCreationDate, phaseTime, actionTime, stepTime, snapshotRepository, snapshotName, snapshotIndexName);
434456
}
435457
}
436458

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

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,13 @@ public class MountSnapshotStep extends AsyncRetryDuringSnapshotActionStep {
3434
private static final Logger logger = LogManager.getLogger(MountSnapshotStep.class);
3535

3636
private final String restoredIndexPrefix;
37+
private final MountSearchableSnapshotRequest.Storage storageType;
3738

38-
public MountSnapshotStep(StepKey key, StepKey nextStepKey, Client client, String restoredIndexPrefix) {
39+
public MountSnapshotStep(StepKey key, StepKey nextStepKey, Client client, String restoredIndexPrefix,
40+
MountSearchableSnapshotRequest.Storage storageType) {
3941
super(key, nextStepKey, client);
4042
this.restoredIndexPrefix = restoredIndexPrefix;
43+
this.storageType = Objects.requireNonNull(storageType, "a storage type must be specified");
4144
}
4245

4346
@Override
@@ -49,9 +52,13 @@ public String getRestoredIndexPrefix() {
4952
return restoredIndexPrefix;
5053
}
5154

55+
public MountSearchableSnapshotRequest.Storage getStorage() {
56+
return storageType;
57+
}
58+
5259
@Override
5360
void performDuringNoSnapshot(IndexMetadata indexMetadata, ClusterState currentClusterState, Listener listener) {
54-
final String indexName = indexMetadata.getIndex().getName();
61+
String indexName = indexMetadata.getIndex().getName();
5562

5663
LifecycleExecutionState lifecycleState = fromIndexMetadata(indexMetadata);
5764

@@ -71,13 +78,29 @@ void performDuringNoSnapshot(IndexMetadata indexMetadata, ClusterState currentCl
7178
}
7279

7380
String mountedIndexName = restoredIndexPrefix + indexName;
74-
if(currentClusterState.metadata().index(mountedIndexName) != null) {
81+
if (currentClusterState.metadata().index(mountedIndexName) != null) {
7582
logger.debug("mounted index [{}] for policy [{}] and index [{}] already exists. will not attempt to mount the index again",
7683
mountedIndexName, policyName, indexName);
7784
listener.onResponse(true);
7885
return;
7986
}
8087

88+
final String snapshotIndexName = lifecycleState.getSnapshotIndexName();
89+
if (snapshotIndexName == null) {
90+
// This index had its searchable snapshot created prior to a version where we captured
91+
// the original index name, so make our best guess at the name
92+
indexName = bestEffortIndexNameResolution(indexName);
93+
logger.debug("index [{}] using policy [{}] does not have a stored snapshot index name, " +
94+
"using our best effort guess of [{}] for the original snapshotted index name",
95+
indexMetadata.getIndex().getName(), policyName, indexName);
96+
} else {
97+
// Use the name of the snapshot as specified in the metadata, because the current index
98+
// name not might not reflect the name of the index actually in the snapshot
99+
logger.debug("index [{}] using policy [{}] has a different name [{}] within the snapshot to be restored, " +
100+
"using the snapshot index name from generated metadata for mounting", indexName, policyName, snapshotIndexName);
101+
indexName = snapshotIndexName;
102+
}
103+
81104
final MountSearchableSnapshotRequest mountSearchableSnapshotRequest = new MountSearchableSnapshotRequest(mountedIndexName,
82105
snapshotRepository, snapshotName, indexName, Settings.builder()
83106
.put(IndexSettings.INDEX_CHECK_ON_STARTUP.getKey(), Boolean.FALSE.toString())
@@ -91,8 +114,7 @@ void performDuringNoSnapshot(IndexMetadata indexMetadata, ClusterState currentCl
91114
// we'll not wait for the snapshot to complete in this step as the async steps are executed from threads that shouldn't
92115
// perform expensive operations (ie. clusterStateProcessed)
93116
false,
94-
// restoring into the cold tier, so use a full local copy
95-
MountSearchableSnapshotRequest.Storage.FULL_COPY);
117+
storageType);
96118
getClient().execute(MountSearchableSnapshotAction.INSTANCE, mountSearchableSnapshotRequest,
97119
ActionListener.wrap(response -> {
98120
if (response.status() != RestStatus.OK && response.status() != RestStatus.ACCEPTED) {
@@ -103,9 +125,21 @@ void performDuringNoSnapshot(IndexMetadata indexMetadata, ClusterState currentCl
103125
}, listener::onFailure));
104126
}
105127

128+
/**
129+
* Tries to guess the original index name given the current index name, tries to drop the
130+
* "partial-" and "restored-" prefixes, since those are what ILM uses. Does not handle
131+
* unorthodox cases like "restored-partial-[indexname]" since this is not intended to be
132+
* exhaustive.
133+
*/
134+
static String bestEffortIndexNameResolution(String indexName) {
135+
String originalName = indexName.replaceFirst("^" + SearchableSnapshotAction.PARTIAL_RESTORED_INDEX_PREFIX, "");
136+
originalName = originalName.replaceFirst("^" + SearchableSnapshotAction.FULL_RESTORED_INDEX_PREFIX, "");
137+
return originalName;
138+
}
139+
106140
@Override
107141
public int hashCode() {
108-
return Objects.hash(super.hashCode(), restoredIndexPrefix);
142+
return Objects.hash(super.hashCode(), restoredIndexPrefix, storageType);
109143
}
110144

111145
@Override
@@ -117,6 +151,8 @@ public boolean equals(Object obj) {
117151
return false;
118152
}
119153
MountSnapshotStep other = (MountSnapshotStep) obj;
120-
return super.equals(obj) && Objects.equals(restoredIndexPrefix, other.restoredIndexPrefix);
154+
return super.equals(obj) &&
155+
Objects.equals(restoredIndexPrefix, other.restoredIndexPrefix) &&
156+
Objects.equals(storageType, other.storageType);
121157
}
122158
}

0 commit comments

Comments
 (0)