Skip to content

Commit 800ae51

Browse files
authored
ILM: searchable snapshot executes before migrate in cold/frozen (#68861)
This moves the execution of the `searchable_snapshot` action before the `migrate` action in the `cold` and `frozen` phases for more efficient data migration (ie. mounting it as a searchable snapshot directly on the target tier) Now that searchable_snapshot can precede other actions in the same phase (eg. in frozen it is followed by `migrate`) we need to allow the mounted index to resume executing the ILM policy starting with a step that's part of a new action (ie. migrate). This adds support to resume the execution of the mounted index from another action. With older versions, the execution would resume from the PhaseCompleteStep as it was the last action in a phase, which was handled as a special case in the `CopyExecutionStateStep`. This generalises the `CopyExecutionStateStep` to be able to resume from any `StepKey`.
1 parent 81afc66 commit 800ae51

File tree

10 files changed

+218
-50
lines changed

10 files changed

+218
-50
lines changed

docs/reference/ilm/actions/ilm-migrate.asciidoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ replicas, {ilm-init} reduces the number of replicas before migrating the index.
1414
To prevent automatic migration without specifying allocation options,
1515
you can explicitly include the migrate action and set the enabled option to `false`.
1616

17+
If the `cold` phase defines a <<ilm-searchable-snapshot, searchable snapshot action>> the `migrate`
18+
action will not be injected automatically in the `cold` phase because the managed index will be
19+
mounted directly on the target tier using the same <<tier-preference-allocation-filter, _tier_preference>>
20+
infrastructure the `migrate` actions configures.
21+
1722
In the warm phase, the `migrate` action sets <<tier-preference-allocation-filter, `index.routing.allocation.include._tier_preference`>>
1823
to `data_warm,data_hot`. This moves the index to nodes in the
1924
<<warm-tier, warm tier>>. If there are no nodes in the warm tier, it falls back to the

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

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@
2121
/**
2222
* Copies the execution state data from one index to another, typically after a
2323
* new index has been created. As part of the execution state copy it will set the target index
24-
* "current step" to the provided step name (part of the same phase and action as the current step's, unless
25-
* the "complete" step is configured in which case the action will be changed to "complete" as well)
24+
* "current step" to the provided target next step {@link org.elasticsearch.xpack.core.ilm.Step.StepKey}.
2625
*
2726
* Useful for actions such as shrink.
2827
*/
@@ -32,20 +31,20 @@ public class CopyExecutionStateStep extends ClusterStateActionStep {
3231
private static final Logger logger = LogManager.getLogger(CopyExecutionStateStep.class);
3332

3433
private final String targetIndexPrefix;
35-
private final String targetNextStepName;
34+
private final StepKey targetNextStepKey;
3635

37-
public CopyExecutionStateStep(StepKey key, StepKey nextStepKey, String targetIndexPrefix, String targetNextStepName) {
36+
public CopyExecutionStateStep(StepKey key, StepKey nextStepKey, String targetIndexPrefix, StepKey targetNextStepKey) {
3837
super(key, nextStepKey);
3938
this.targetIndexPrefix = targetIndexPrefix;
40-
this.targetNextStepName = targetNextStepName;
39+
this.targetNextStepKey = targetNextStepKey;
4140
}
4241

4342
String getTargetIndexPrefix() {
4443
return targetIndexPrefix;
4544
}
4645

47-
String getTargetNextStepName() {
48-
return targetNextStepName;
46+
StepKey getTargetNextStepKey() {
47+
return targetNextStepKey;
4948
}
5049

5150
@Override
@@ -69,20 +68,17 @@ public ClusterState performAction(Index index, ClusterState clusterState) {
6968
"] to [" + targetIndexName + "] as target index does not exist");
7069
}
7170

71+
String phase = targetNextStepKey.getPhase();
72+
String action = targetNextStepKey.getAction();
73+
String step = targetNextStepKey.getName();
7274
LifecycleExecutionState lifecycleState = LifecycleExecutionState.fromIndexMetadata(indexMetadata);
73-
String phase = lifecycleState.getPhase();
74-
String action = lifecycleState.getAction();
7575
long lifecycleDate = lifecycleState.getLifecycleDate();
7676

7777
LifecycleExecutionState.Builder relevantTargetCustomData = LifecycleExecutionState.builder();
7878
relevantTargetCustomData.setIndexCreationDate(lifecycleDate);
79+
relevantTargetCustomData.setAction(action);
7980
relevantTargetCustomData.setPhase(phase);
80-
relevantTargetCustomData.setStep(targetNextStepName);
81-
if (targetNextStepName.equals(PhaseCompleteStep.NAME)) {
82-
relevantTargetCustomData.setAction(PhaseCompleteStep.NAME);
83-
} else {
84-
relevantTargetCustomData.setAction(action);
85-
}
81+
relevantTargetCustomData.setStep(step);
8682
relevantTargetCustomData.setSnapshotRepository(lifecycleState.getSnapshotRepository());
8783
relevantTargetCustomData.setSnapshotName(lifecycleState.getSnapshotName());
8884
relevantTargetCustomData.setSnapshotIndexName(lifecycleState.getSnapshotIndexName());
@@ -107,11 +103,11 @@ public boolean equals(Object o) {
107103
}
108104
CopyExecutionStateStep that = (CopyExecutionStateStep) o;
109105
return Objects.equals(targetIndexPrefix, that.targetIndexPrefix) &&
110-
Objects.equals(targetNextStepName, that.targetNextStepName);
106+
Objects.equals(targetNextStepKey, that.targetNextStepKey);
111107
}
112108

113109
@Override
114110
public int hashCode() {
115-
return Objects.hash(super.hashCode(), targetIndexPrefix, targetNextStepName);
111+
return Objects.hash(super.hashCode(), targetIndexPrefix, targetNextStepKey);
116112
}
117113
}

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

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
*/
77
package org.elasticsearch.xpack.core.ilm;
88

9+
import org.apache.logging.log4j.LogManager;
10+
import org.apache.logging.log4j.Logger;
911
import org.elasticsearch.client.Client;
12+
import org.elasticsearch.cluster.metadata.IndexMetadata;
1013
import org.elasticsearch.common.ParseField;
1114
import org.elasticsearch.common.Strings;
1215
import org.elasticsearch.common.io.stream.StreamInput;
@@ -25,6 +28,8 @@
2528
import java.util.Objects;
2629
import java.util.stream.Collectors;
2730

31+
import static org.elasticsearch.xpack.core.ilm.TimeseriesLifecycleType.FROZEN_PHASE;
32+
2833
/**
2934
* A {@link LifecycleAction} which enables or disables the automatic migration of data between
3035
* {@link org.elasticsearch.xpack.core.DataTier}s.
@@ -33,6 +38,8 @@ public class MigrateAction implements LifecycleAction {
3338
public static final String NAME = "migrate";
3439
public static final ParseField ENABLED_FIELD = new ParseField("enabled");
3540

41+
private static final Logger logger = LogManager.getLogger(MigrateAction.class);
42+
static final String CONDITIONAL_SKIP_MIGRATE_STEP = BranchingStep.NAME + "-check-skip-action";
3643
// Represents an ordered list of data tiers from frozen to hot (or slow to fast)
3744
private static final List<String> FROZEN_TO_HOT_TIERS =
3845
List.of(DataTier.DATA_FROZEN, DataTier.DATA_COLD, DataTier.DATA_WARM, DataTier.DATA_HOT);
@@ -92,22 +99,44 @@ public boolean isSafeAction() {
9299
@Override
93100
public List<Step> toSteps(Client client, String phase, StepKey nextStepKey) {
94101
if (enabled) {
102+
StepKey preMigrateBranchingKey = new StepKey(phase, NAME, CONDITIONAL_SKIP_MIGRATE_STEP);
95103
StepKey migrationKey = new StepKey(phase, NAME, NAME);
96104
StepKey migrationRoutedKey = new StepKey(phase, NAME, DataTierMigrationRoutedStep.NAME);
97105

98106
Settings.Builder migrationSettings = Settings.builder();
99-
String dataTierName = "data_" + phase;
100-
assert DataTier.validTierName(dataTierName) : "invalid data tier name:" + dataTierName;
101-
migrationSettings.put(DataTierAllocationDecider.INDEX_ROUTING_PREFER, getPreferredTiersConfiguration(dataTierName));
107+
String targetTier = "data_" + phase;
108+
assert DataTier.validTierName(targetTier) : "invalid data tier name:" + targetTier;
109+
110+
BranchingStep conditionalSkipActionStep = new BranchingStep(preMigrateBranchingKey, migrationKey, nextStepKey,
111+
(index, clusterState) -> {
112+
if (skipMigrateAction(phase, clusterState.metadata().index(index))) {
113+
String policyName =
114+
LifecycleSettings.LIFECYCLE_NAME_SETTING.get(clusterState.metadata().index(index).getSettings());
115+
logger.debug("[{}] action is configured for index [{}] in policy [{}] which is already mounted as a searchable " +
116+
"snapshot. skipping this action", MigrateAction.NAME, index.getName(), policyName);
117+
return true;
118+
}
119+
120+
// don't skip the migrate action as the index is not mounted as searchable snapshot or we're in the frozen phase
121+
return false;
122+
});
123+
migrationSettings.put(DataTierAllocationDecider.INDEX_ROUTING_PREFER, getPreferredTiersConfiguration(targetTier));
102124
UpdateSettingsStep updateMigrationSettingStep = new UpdateSettingsStep(migrationKey, migrationRoutedKey, client,
103125
migrationSettings.build());
104126
DataTierMigrationRoutedStep migrationRoutedStep = new DataTierMigrationRoutedStep(migrationRoutedKey, nextStepKey);
105-
return Arrays.asList(updateMigrationSettingStep, migrationRoutedStep);
127+
return Arrays.asList(conditionalSkipActionStep, updateMigrationSettingStep, migrationRoutedStep);
106128
} else {
107129
return List.of();
108130
}
109131
}
110132

133+
static boolean skipMigrateAction(String phase, IndexMetadata indexMetadata) {
134+
// if the index is a searchable snapshot we skip the migrate action (as mounting an index as searchable snapshot
135+
// configures the tier allocation preference), unless we're in the frozen phase
136+
return (indexMetadata.getSettings().get(LifecycleSettings.SNAPSHOT_INDEX_NAME) != null)
137+
&& (phase.equals(FROZEN_PHASE) == false);
138+
}
139+
111140
/**
112141
* Based on the provided target tier it will return a comma separated list of preferred tiers.
113142
* ie. if `data_cold` is the target tier, it will return `data_cold,data_warm,data_hot`

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,10 +245,8 @@ public List<Step> toSteps(Client client, String phase, StepKey nextStepKey) {
245245
client, getRestoredIndexPrefix(mountSnapshotKey), getConcreteStorageType(mountSnapshotKey));
246246
WaitForIndexColorStep waitForGreenIndexHealthStep = new WaitForIndexColorStep(waitForGreenRestoredIndexKey,
247247
copyMetadataKey, ClusterHealthStatus.GREEN, getRestoredIndexPrefix(waitForGreenRestoredIndexKey));
248-
// a policy with only the cold phase will have a null "nextStepKey", hence the "null" nextStepKey passed in below when that's the
249-
// case
250248
CopyExecutionStateStep copyMetadataStep = new CopyExecutionStateStep(copyMetadataKey, copyLifecyclePolicySettingKey,
251-
getRestoredIndexPrefix(copyMetadataKey), nextStepKey != null ? nextStepKey.getName() : "null");
249+
getRestoredIndexPrefix(copyMetadataKey), nextStepKey);
252250
CopySettingsStep copySettingsStep = new CopySettingsStep(copyLifecyclePolicySettingKey, dataStreamCheckBranchingKey,
253251
getRestoredIndexPrefix(copyLifecyclePolicySettingKey), LifecycleSettings.LIFECYCLE_NAME);
254252
BranchingStep isDataStreamBranchingStep = new BranchingStep(dataStreamCheckBranchingKey, swapAliasesKey, replaceDataStreamIndexKey,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ public List<Step> toSteps(Client client, String phase, Step.StepKey nextStepKey)
184184
SHRUNKEN_INDEX_PREFIX);
185185
ShrunkShardsAllocatedStep allocated = new ShrunkShardsAllocatedStep(enoughShardsKey, copyMetadataKey, SHRUNKEN_INDEX_PREFIX);
186186
CopyExecutionStateStep copyMetadata = new CopyExecutionStateStep(copyMetadataKey, dataStreamCheckBranchingKey,
187-
SHRUNKEN_INDEX_PREFIX, ShrunkenIndexCheckStep.NAME);
187+
SHRUNKEN_INDEX_PREFIX, isShrunkIndexKey);
188188
// by the time we get to this step we have 2 indices, the source and the shrunken one. we now need to choose an index
189189
// swapping strategy such that the shrunken index takes the place of the source index (which is also deleted).
190190
// if the source index is part of a data stream it's a matter of replacing it with the shrunken index one in the data stream and

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

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ public class TimeseriesLifecycleType implements LifecycleType {
4747
static final List<String> ORDERED_VALID_WARM_ACTIONS = Arrays.asList(SetPriorityAction.NAME, UnfollowAction.NAME, ReadOnlyAction.NAME,
4848
AllocateAction.NAME, MigrateAction.NAME, ShrinkAction.NAME, ForceMergeAction.NAME);
4949
static final List<String> ORDERED_VALID_COLD_ACTIONS;
50-
static final List<String> ORDERED_VALID_FROZEN_ACTIONS = Arrays.asList(SetPriorityAction.NAME, UnfollowAction.NAME, ReadOnlyAction.NAME,
51-
AllocateAction.NAME, MigrateAction.NAME, FreezeAction.NAME, SearchableSnapshotAction.NAME);
50+
static final List<String> ORDERED_VALID_FROZEN_ACTIONS = Arrays.asList(SetPriorityAction.NAME, UnfollowAction.NAME,
51+
ReadOnlyAction.NAME, SearchableSnapshotAction.NAME, AllocateAction.NAME, MigrateAction.NAME, FreezeAction.NAME);
5252
static final List<String> ORDERED_VALID_DELETE_ACTIONS = Arrays.asList(WaitForSnapshotAction.NAME, DeleteAction.NAME);
5353
static final Set<String> VALID_HOT_ACTIONS;
5454
static final Set<String> VALID_WARM_ACTIONS = Sets.newHashSet(ORDERED_VALID_WARM_ACTIONS);
@@ -67,13 +67,13 @@ public class TimeseriesLifecycleType implements LifecycleType {
6767
if (RollupV2.isEnabled()) {
6868
ORDERED_VALID_HOT_ACTIONS = Arrays.asList(SetPriorityAction.NAME, UnfollowAction.NAME, RolloverAction.NAME,
6969
ReadOnlyAction.NAME, RollupILMAction.NAME, ShrinkAction.NAME, ForceMergeAction.NAME, SearchableSnapshotAction.NAME);
70-
ORDERED_VALID_COLD_ACTIONS = Arrays.asList(SetPriorityAction.NAME, UnfollowAction.NAME, AllocateAction.NAME,
71-
MigrateAction.NAME, FreezeAction.NAME, RollupILMAction.NAME, SearchableSnapshotAction.NAME);
70+
ORDERED_VALID_COLD_ACTIONS = Arrays.asList(SetPriorityAction.NAME, UnfollowAction.NAME, SearchableSnapshotAction.NAME,
71+
AllocateAction.NAME, MigrateAction.NAME, FreezeAction.NAME, RollupILMAction.NAME);
7272
} else {
7373
ORDERED_VALID_HOT_ACTIONS = Arrays.asList(SetPriorityAction.NAME, UnfollowAction.NAME, RolloverAction.NAME,
7474
ReadOnlyAction.NAME, ShrinkAction.NAME, ForceMergeAction.NAME, SearchableSnapshotAction.NAME);
75-
ORDERED_VALID_COLD_ACTIONS = Arrays.asList(SetPriorityAction.NAME, UnfollowAction.NAME, AllocateAction.NAME,
76-
MigrateAction.NAME, FreezeAction.NAME, SearchableSnapshotAction.NAME);
75+
ORDERED_VALID_COLD_ACTIONS = Arrays.asList(SetPriorityAction.NAME, UnfollowAction.NAME, SearchableSnapshotAction.NAME,
76+
AllocateAction.NAME, MigrateAction.NAME, FreezeAction.NAME);
7777
}
7878
VALID_HOT_ACTIONS = Sets.newHashSet(ORDERED_VALID_HOT_ACTIONS);
7979
VALID_COLD_ACTIONS = Sets.newHashSet(ORDERED_VALID_COLD_ACTIONS);
@@ -133,6 +133,13 @@ static boolean shouldInjectMigrateStepForPhase(Phase phase) {
133133
}
134134
}
135135

136+
if (phase.getActions().get(SearchableSnapshotAction.NAME) != null && phase.getName().equals(FROZEN_PHASE) == false) {
137+
// the `searchable_snapshot` action defines migration rules itself, so no need to inject a migrate action, unless we're in the
138+
// frozen phase (as the migrate action would also include the `data_frozen` role which is not guaranteed to be included by all
139+
// types of searchable snapshots)
140+
return false;
141+
}
142+
136143
MigrateAction migrateAction = (MigrateAction) phase.getActions().get(MigrateAction.NAME);
137144
// if the user configured the {@link MigrateAction} already we won't automatically configure it
138145
return migrateAction == null;

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/CopyExecutionStateStepTests.java

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,16 @@ protected CopyExecutionStateStep createRandomInstance() {
2626
StepKey stepKey = randomStepKey();
2727
StepKey nextStepKey = randomStepKey();
2828
String shrunkIndexPrefix = randomAlphaOfLength(10);
29-
String nextStepName = randomStepKey().getName();
30-
return new CopyExecutionStateStep(stepKey, nextStepKey, shrunkIndexPrefix, nextStepName);
29+
StepKey targetNextStepKey = randomStepKey();
30+
return new CopyExecutionStateStep(stepKey, nextStepKey, shrunkIndexPrefix, targetNextStepKey);
3131
}
3232

3333
@Override
3434
protected CopyExecutionStateStep mutateInstance(CopyExecutionStateStep instance) {
3535
StepKey key = instance.getKey();
3636
StepKey nextKey = instance.getNextStepKey();
3737
String shrunkIndexPrefix = instance.getTargetIndexPrefix();
38-
String nextStepName = instance.getTargetNextStepName();
38+
StepKey targetNextStepKey = instance.getTargetNextStepKey();
3939

4040
switch (between(0, 2)) {
4141
case 0:
@@ -48,19 +48,20 @@ protected CopyExecutionStateStep mutateInstance(CopyExecutionStateStep instance)
4848
shrunkIndexPrefix += randomAlphaOfLength(5);
4949
break;
5050
case 3:
51-
nextStepName = randomAlphaOfLengthBetween(1, 10);
51+
targetNextStepKey = new StepKey(targetNextStepKey.getPhase(), targetNextStepKey.getAction(),
52+
targetNextStepKey.getName() + randomAlphaOfLength(5));
5253
break;
5354
default:
5455
throw new AssertionError("Illegal randomisation branch");
5556
}
5657

57-
return new CopyExecutionStateStep(key, nextKey, shrunkIndexPrefix, nextStepName);
58+
return new CopyExecutionStateStep(key, nextKey, shrunkIndexPrefix, targetNextStepKey);
5859
}
5960

6061
@Override
6162
protected CopyExecutionStateStep copyInstance(CopyExecutionStateStep instance) {
6263
return new CopyExecutionStateStep(instance.getKey(), instance.getNextStepKey(), instance.getTargetIndexPrefix(),
63-
instance.getTargetNextStepName());
64+
instance.getTargetNextStepKey());
6465
}
6566

6667
public void testPerformAction() {
@@ -89,10 +90,11 @@ public void testPerformAction() {
8990
LifecycleExecutionState newIndexData = LifecycleExecutionState
9091
.fromIndexMetadata(newClusterState.metadata().index(step.getTargetIndexPrefix() + indexName));
9192

93+
StepKey targetNextStepKey = step.getTargetNextStepKey();
9294
assertEquals(newIndexData.getLifecycleDate(), oldIndexData.getLifecycleDate());
93-
assertEquals(newIndexData.getPhase(), oldIndexData.getPhase());
94-
assertEquals(newIndexData.getAction(), oldIndexData.getAction());
95-
assertEquals(newIndexData.getStep(), step.getTargetNextStepName());
95+
assertEquals(newIndexData.getPhase(), targetNextStepKey.getPhase());
96+
assertEquals(newIndexData.getAction(), targetNextStepKey.getAction());
97+
assertEquals(newIndexData.getStep(), targetNextStepKey.getName());
9698
assertEquals(newIndexData.getSnapshotRepository(), oldIndexData.getSnapshotRepository());
9799
assertEquals(newIndexData.getSnapshotName(), oldIndexData.getSnapshotName());
98100
}

0 commit comments

Comments
 (0)