Skip to content

Commit 9986cb8

Browse files
authored
Allow shrink in the hot phase for ILM policies (#64008)
1 parent df89c27 commit 9986cb8

File tree

7 files changed

+138
-71
lines changed

7 files changed

+138
-71
lines changed

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

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
[[ilm-shrink]]
33
=== Shrink
44

5-
Phases allowed: warm
5+
Phases allowed: hot, warm.
66

77
Sets an index to <<dynamic-index-settings, read-only>>
88
and shrinks it into a new index with fewer primary shards.
@@ -11,9 +11,12 @@ For example, if the name of the source index is _logs_,
1111
the name of the shrunken index is _shrink-logs_.
1212

1313
The shrink action allocates all primary shards of the index to one node so it
14-
can call the <<indices-shrink-index,Shrink API>> to shrink the index.
14+
can call the <<indices-shrink-index,Shrink API>> to shrink the index.
1515
After shrinking, it swaps aliases that point to the original index to the new shrunken index.
1616

17+
To use the `shrink` action in the `hot` phase, the `rollover` action *must* be present.
18+
If no rollover action is configured, {ilm-init} will reject the policy.
19+
1720
[IMPORTANT]
1821
If the shrink action is used on a <<ccr-put-follow,follower index>>,
1922
policy execution waits until the leader index rolls over (or is

docs/reference/ilm/ilm-index-lifecycle.asciidoc

+4-2
Original file line numberDiff line numberDiff line change
@@ -77,22 +77,24 @@ the rollover criteria, it could be 20 minutes before the rollover is complete.
7777
* Hot
7878
- <<ilm-set-priority,Set Priority>>
7979
- <<ilm-unfollow,Unfollow>>
80-
- <<ilm-forcemerge,Force Merge>>
8180
- <<ilm-rollover,Rollover>>
81+
- <<ilm-shrink,Shrink>>
82+
- <<ilm-forcemerge,Force Merge>>
8283
* Warm
8384
- <<ilm-set-priority,Set Priority>>
8485
- <<ilm-unfollow,Unfollow>>
8586
- <<ilm-readonly,Read-Only>>
8687
- <<ilm-allocate,Allocate>>
88+
- <<ilm-migrate,Migrate>>
8789
- <<ilm-shrink,Shrink>>
8890
- <<ilm-forcemerge,Force Merge>>
8991
* Cold
9092
- <<ilm-set-priority-action,Set Priority>>
9193
- <<ilm-unfollow-action,Unfollow>>
9294
- <<ilm-allocate,Allocate>>
95+
- <<ilm-migrate,Migrate>>
9396
- <<ilm-freeze,Freeze>>
9497
- <<ilm-searchable-snapshot, Searchable Snapshot>>
9598
* Delete
9699
- <<ilm-wait-for-snapshot-action,Wait For Snapshot>>
97100
- <<ilm-delete,Delete>>
98-

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

+35-35
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public class TimeseriesLifecycleType implements LifecycleType {
4040
static final String DELETE_PHASE = "delete";
4141
static final List<String> VALID_PHASES = Arrays.asList(HOT_PHASE, WARM_PHASE, COLD_PHASE, DELETE_PHASE);
4242
static final List<String> ORDERED_VALID_HOT_ACTIONS = Arrays.asList(SetPriorityAction.NAME, UnfollowAction.NAME, RolloverAction.NAME,
43-
ForceMergeAction.NAME);
43+
ShrinkAction.NAME, ForceMergeAction.NAME);
4444
static final List<String> ORDERED_VALID_WARM_ACTIONS = Arrays.asList(SetPriorityAction.NAME, UnfollowAction.NAME, ReadOnlyAction.NAME,
4545
AllocateAction.NAME, MigrateAction.NAME, ShrinkAction.NAME, ForceMergeAction.NAME);
4646
static final List<String> ORDERED_VALID_COLD_ACTIONS = Arrays.asList(SetPriorityAction.NAME, UnfollowAction.NAME, AllocateAction.NAME,
@@ -50,14 +50,13 @@ public class TimeseriesLifecycleType implements LifecycleType {
5050
static final Set<String> VALID_WARM_ACTIONS = Sets.newHashSet(ORDERED_VALID_WARM_ACTIONS);
5151
static final Set<String> VALID_COLD_ACTIONS = Sets.newHashSet(ORDERED_VALID_COLD_ACTIONS);
5252
static final Set<String> VALID_DELETE_ACTIONS = Sets.newHashSet(ORDERED_VALID_DELETE_ACTIONS);
53-
private static final Map<String, Set<String>> ALLOWED_ACTIONS = new HashMap<>();
53+
private static final Map<String, Set<String>> ALLOWED_ACTIONS = Map.of(
54+
HOT_PHASE, VALID_HOT_ACTIONS,
55+
WARM_PHASE, VALID_WARM_ACTIONS,
56+
COLD_PHASE, VALID_COLD_ACTIONS,
57+
DELETE_PHASE, VALID_DELETE_ACTIONS);
5458

55-
static {
56-
ALLOWED_ACTIONS.put(HOT_PHASE, VALID_HOT_ACTIONS);
57-
ALLOWED_ACTIONS.put(WARM_PHASE, VALID_WARM_ACTIONS);
58-
ALLOWED_ACTIONS.put(COLD_PHASE, VALID_COLD_ACTIONS);
59-
ALLOWED_ACTIONS.put(DELETE_PHASE, VALID_DELETE_ACTIONS);
60-
}
59+
static final Set<String> HOT_ACTIONS_THAT_REQUIRE_ROLLOVER = Sets.newHashSet(ShrinkAction.NAME, ForceMergeAction.NAME);
6160

6261
private TimeseriesLifecycleType() {
6362
}
@@ -157,16 +156,16 @@ public List<LifecycleAction> getOrderedActions(Phase phase) {
157156
Map<String, LifecycleAction> actions = phase.getActions();
158157
switch (phase.getName()) {
159158
case HOT_PHASE:
160-
return ORDERED_VALID_HOT_ACTIONS.stream().map(a -> actions.getOrDefault(a, null))
159+
return ORDERED_VALID_HOT_ACTIONS.stream().map(actions::get)
161160
.filter(Objects::nonNull).collect(toList());
162161
case WARM_PHASE:
163-
return ORDERED_VALID_WARM_ACTIONS.stream().map(a -> actions.getOrDefault(a, null))
162+
return ORDERED_VALID_WARM_ACTIONS.stream().map(actions::get)
164163
.filter(Objects::nonNull).collect(toList());
165164
case COLD_PHASE:
166-
return ORDERED_VALID_COLD_ACTIONS.stream().map(a -> actions.getOrDefault(a, null))
165+
return ORDERED_VALID_COLD_ACTIONS.stream().map(actions::get)
167166
.filter(Objects::nonNull).collect(toList());
168167
case DELETE_PHASE:
169-
return ORDERED_VALID_DELETE_ACTIONS.stream().map(a -> actions.getOrDefault(a, null))
168+
return ORDERED_VALID_DELETE_ACTIONS.stream().map(actions::get)
170169
.filter(Objects::nonNull).collect(toList());
171170
default:
172171
throw new IllegalArgumentException("lifecycle type[" + TYPE + "] does not support phase[" + phase.getName() + "]");
@@ -177,20 +176,20 @@ public List<LifecycleAction> getOrderedActions(Phase phase) {
177176
public String getNextActionName(String currentActionName, Phase phase) {
178177
List<String> orderedActionNames;
179178
switch (phase.getName()) {
180-
case HOT_PHASE:
181-
orderedActionNames = ORDERED_VALID_HOT_ACTIONS;
182-
break;
183-
case WARM_PHASE:
184-
orderedActionNames = ORDERED_VALID_WARM_ACTIONS;
185-
break;
186-
case COLD_PHASE:
187-
orderedActionNames = ORDERED_VALID_COLD_ACTIONS;
188-
break;
189-
case DELETE_PHASE:
190-
orderedActionNames = ORDERED_VALID_DELETE_ACTIONS;
191-
break;
192-
default:
193-
throw new IllegalArgumentException("lifecycle type [" + TYPE + "] does not support phase [" + phase.getName() + "]");
179+
case HOT_PHASE:
180+
orderedActionNames = ORDERED_VALID_HOT_ACTIONS;
181+
break;
182+
case WARM_PHASE:
183+
orderedActionNames = ORDERED_VALID_WARM_ACTIONS;
184+
break;
185+
case COLD_PHASE:
186+
orderedActionNames = ORDERED_VALID_COLD_ACTIONS;
187+
break;
188+
case DELETE_PHASE:
189+
orderedActionNames = ORDERED_VALID_DELETE_ACTIONS;
190+
break;
191+
default:
192+
throw new IllegalArgumentException("lifecycle type [" + TYPE + "] does not support phase [" + phase.getName() + "]");
194193
}
195194

196195
int index = orderedActionNames.indexOf(currentActionName);
@@ -226,17 +225,18 @@ public void validate(Collection<Phase> phases) {
226225
});
227226
});
228227

229-
// Check for forcemerge in 'hot' without a rollover action
230-
if (phases.stream()
228+
// Check for actions in the hot phase that require a rollover
229+
String invalidHotPhaseActions = phases.stream()
231230
// Is there a hot phase
232231
.filter(phase -> HOT_PHASE.equals(phase.getName()))
233-
// That contains the 'forcemerge' action
234-
.filter(phase -> phase.getActions().containsKey(ForceMergeAction.NAME))
235-
// But does *not* contain the 'rollover' action?
236-
.anyMatch(phase -> phase.getActions().containsKey(RolloverAction.NAME) == false)) {
237-
// If there is, throw an exception
238-
throw new IllegalArgumentException("the [" + ForceMergeAction.NAME +
239-
"] action may not be used in the [" + HOT_PHASE +
232+
// that does *not* contain the 'rollover' action
233+
.filter(phase -> phase.getActions().containsKey(RolloverAction.NAME) == false)
234+
// but that does have actions that require a rollover action?
235+
.flatMap(phase -> Sets.intersection(phase.getActions().keySet(), HOT_ACTIONS_THAT_REQUIRE_ROLLOVER).stream())
236+
.collect(Collectors.joining(", "));
237+
if (Strings.hasText(invalidHotPhaseActions)) {
238+
throw new IllegalArgumentException("the [" + invalidHotPhaseActions +
239+
"] action(s) may not be used in the [" + HOT_PHASE +
240240
"] phase without an accompanying [" + RolloverAction.NAME + "] action");
241241
}
242242

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

+30-29
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ protected LifecyclePolicy createTestInstance() {
9999
public static LifecyclePolicy randomTimeseriesLifecyclePolicyWithAllPhases(@Nullable String lifecycleName) {
100100
List<String> phaseNames = TimeseriesLifecycleType.VALID_PHASES;
101101
Map<String, Phase> phases = new HashMap<>(phaseNames.size());
102-
Function<String, Set<String>> validActions = (phase) -> {
102+
Function<String, Set<String>> validActions = (phase) -> {
103103
switch (phase) {
104104
case "hot":
105105
return TimeseriesLifecycleType.VALID_HOT_ACTIONS;
@@ -112,14 +112,14 @@ public static LifecyclePolicy randomTimeseriesLifecyclePolicyWithAllPhases(@Null
112112
default:
113113
throw new IllegalArgumentException("invalid phase [" + phase + "]");
114114
}};
115-
Function<String, LifecycleAction> randomAction = (action) -> {
115+
Function<String, LifecycleAction> randomAction = (action) -> {
116116
switch (action) {
117117
case AllocateAction.NAME:
118118
return AllocateActionTests.randomInstance();
119119
case DeleteAction.NAME:
120120
return new DeleteAction();
121121
case WaitForSnapshotAction.NAME:
122-
return WaitForSnapshotActionTests.randomInstance();
122+
return WaitForSnapshotActionTests.randomInstance();
123123
case ForceMergeAction.NAME:
124124
return ForceMergeActionTests.randomInstance();
125125
case ReadOnlyAction.NAME:
@@ -157,7 +157,7 @@ public static LifecyclePolicy randomTimeseriesLifecyclePolicy(@Nullable String l
157157
List<String> phaseNames = randomSubsetOf(
158158
between(0, TimeseriesLifecycleType.VALID_PHASES.size() - 1), TimeseriesLifecycleType.VALID_PHASES);
159159
Map<String, Phase> phases = new HashMap<>(phaseNames.size());
160-
Function<String, Set<String>> validActions = (phase) -> {
160+
Function<String, Set<String>> validActions = (phase) -> {
161161
switch (phase) {
162162
case "hot":
163163
return TimeseriesLifecycleType.VALID_HOT_ACTIONS;
@@ -170,7 +170,7 @@ public static LifecyclePolicy randomTimeseriesLifecyclePolicy(@Nullable String l
170170
default:
171171
throw new IllegalArgumentException("invalid phase [" + phase + "]");
172172
}};
173-
Function<String, LifecycleAction> randomAction = (action) -> {
173+
Function<String, LifecycleAction> randomAction = (action) -> {
174174
switch (action) {
175175
case AllocateAction.NAME:
176176
return AllocateActionTests.randomInstance();
@@ -204,8 +204,9 @@ public static LifecyclePolicy randomTimeseriesLifecyclePolicy(@Nullable String l
204204
Map<String, LifecycleAction> actions = new HashMap<>();
205205
List<String> actionNames = randomSubsetOf(validActions.apply(phase));
206206

207-
// If the hot phase contains a forcemerge, also make sure to add a rollover, or else the policy will not validate
208-
if (phase.equals(TimeseriesLifecycleType.HOT_PHASE) && actionNames.contains(ForceMergeAction.NAME)) {
207+
// If the hot phase has any actions that require a rollover, then ensure there is one so that the policy will validate
208+
if (phase.equals(TimeseriesLifecycleType.HOT_PHASE)
209+
&& actionNames.stream().anyMatch(TimeseriesLifecycleType.HOT_ACTIONS_THAT_REQUIRE_ROLLOVER::contains)) {
209210
actionNames.add(RolloverAction.NAME);
210211
}
211212

@@ -238,16 +239,16 @@ protected LifecyclePolicy mutateInstance(LifecyclePolicy instance) throws IOExce
238239
String name = instance.getName();
239240
Map<String, Phase> phases = instance.getPhases();
240241
switch (between(0, 1)) {
241-
case 0:
242-
name = name + randomAlphaOfLengthBetween(1, 5);
243-
break;
244-
case 1:
245-
String phaseName = randomValueOtherThanMany(phases::containsKey, () -> randomFrom(TimeseriesLifecycleType.VALID_PHASES));
246-
phases = new LinkedHashMap<>(phases);
247-
phases.put(phaseName, new Phase(phaseName, TimeValue.timeValueSeconds(randomIntBetween(1, 1000)), Collections.emptyMap()));
248-
break;
249-
default:
250-
throw new AssertionError("Illegal randomisation branch");
242+
case 0:
243+
name = name + randomAlphaOfLengthBetween(1, 5);
244+
break;
245+
case 1:
246+
String phaseName = randomValueOtherThanMany(phases::containsKey, () -> randomFrom(TimeseriesLifecycleType.VALID_PHASES));
247+
phases = new LinkedHashMap<>(phases);
248+
phases.put(phaseName, new Phase(phaseName, TimeValue.timeValueSeconds(randomIntBetween(1, 1000)), Collections.emptyMap()));
249+
break;
250+
default:
251+
throw new AssertionError("Illegal randomisation branch");
251252
}
252253
return new LifecyclePolicy(TimeseriesLifecycleType.INSTANCE, name, phases);
253254
}
@@ -300,7 +301,7 @@ public void testToStepsWithTwoPhases() {
300301
MockStep secondActionStep = new MockStep(new StepKey("second_phase", "test2", "test"),
301302
PhaseCompleteStep.finalStep("second_phase").getKey());
302303
MockStep secondAfter = new MockStep(new StepKey("first_phase", PhaseCompleteStep.NAME, PhaseCompleteStep.NAME),
303-
secondActionStep.getKey());
304+
secondActionStep.getKey());
304305
MockStep firstActionAnotherStep = new MockStep(new StepKey("first_phase", "test", "bar"), secondAfter.getKey());
305306
MockStep firstActionStep = new MockStep(new StepKey("first_phase", "test", "foo"), firstActionAnotherStep.getKey());
306307
MockStep firstAfter = new MockStep(new StepKey("new", PhaseCompleteStep.NAME, PhaseCompleteStep.NAME), firstActionStep.getKey());
@@ -352,30 +353,30 @@ public void testIsActionSafe() {
352353
assertFalse(policy.isActionSafe(new StepKey("second_phase", MockAction.NAME, randomAlphaOfLength(10))));
353354

354355
IllegalArgumentException exception = expectThrows(IllegalArgumentException.class,
355-
() -> policy.isActionSafe(new StepKey("non_existant_phase", MockAction.NAME, randomAlphaOfLength(10))));
356+
() -> policy.isActionSafe(new StepKey("non_existant_phase", MockAction.NAME, randomAlphaOfLength(10))));
356357
assertEquals("Phase [non_existant_phase] does not exist in policy [" + policy.getName() + "]", exception.getMessage());
357358

358359
exception = expectThrows(IllegalArgumentException.class,
359-
() -> policy.isActionSafe(new StepKey("first_phase", "non_existant_action", randomAlphaOfLength(10))));
360+
() -> policy.isActionSafe(new StepKey("first_phase", "non_existant_action", randomAlphaOfLength(10))));
360361
assertEquals("Action [non_existant_action] in phase [first_phase] does not exist in policy [" + policy.getName() + "]",
361-
exception.getMessage());
362+
exception.getMessage());
362363

363364
assertTrue(policy.isActionSafe(new StepKey("new", randomAlphaOfLength(10), randomAlphaOfLength(10))));
364365
}
365366

366367
public void testValidatePolicyName() {
367-
expectThrows(IllegalArgumentException.class, () -> LifecyclePolicy.validatePolicyName(randomAlphaOfLengthBetween(0,10) +
368-
"," + randomAlphaOfLengthBetween(0,10)));
369-
expectThrows(IllegalArgumentException.class, () -> LifecyclePolicy.validatePolicyName(randomAlphaOfLengthBetween(0,10) +
370-
" " + randomAlphaOfLengthBetween(0,10)));
368+
expectThrows(IllegalArgumentException.class, () -> LifecyclePolicy.validatePolicyName(randomAlphaOfLengthBetween(0, 10) +
369+
"," + randomAlphaOfLengthBetween(0, 10)));
370+
expectThrows(IllegalArgumentException.class, () -> LifecyclePolicy.validatePolicyName(randomAlphaOfLengthBetween(0, 10) +
371+
" " + randomAlphaOfLengthBetween(0, 10)));
371372
expectThrows(IllegalArgumentException.class, () -> LifecyclePolicy.validatePolicyName("_" + randomAlphaOfLengthBetween(1, 20)));
372373
expectThrows(IllegalArgumentException.class, () -> LifecyclePolicy.validatePolicyName(randomAlphaOfLengthBetween(256, 1000)));
373374

374-
LifecyclePolicy.validatePolicyName(randomAlphaOfLengthBetween(1,10) + "_" + randomAlphaOfLengthBetween(0,10));
375+
LifecyclePolicy.validatePolicyName(randomAlphaOfLengthBetween(1, 10) + "_" + randomAlphaOfLengthBetween(0, 10));
375376

376-
LifecyclePolicy.validatePolicyName(randomAlphaOfLengthBetween(0,10) + "-" + randomAlphaOfLengthBetween(0,10));
377-
LifecyclePolicy.validatePolicyName(randomAlphaOfLengthBetween(0,10) + "+" + randomAlphaOfLengthBetween(0,10));
377+
LifecyclePolicy.validatePolicyName(randomAlphaOfLengthBetween(0, 10) + "-" + randomAlphaOfLengthBetween(0, 10));
378+
LifecyclePolicy.validatePolicyName(randomAlphaOfLengthBetween(0, 10) + "+" + randomAlphaOfLengthBetween(0, 10));
378379

379-
LifecyclePolicy.validatePolicyName(randomAlphaOfLengthBetween(1,255));
380+
LifecyclePolicy.validatePolicyName(randomAlphaOfLengthBetween(1, 255));
380381
}
381382
}

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public void testValidateHotPhase() {
104104
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
105105
() -> validateHotActions.accept(Arrays.asList(ForceMergeAction.NAME)));
106106
assertThat(e.getMessage(),
107-
containsString("the [forcemerge] action may not be used in the [hot] phase without an accompanying [rollover] action"));
107+
containsString("the [forcemerge] action(s) may not be used in the [hot] phase without an accompanying [rollover] action"));
108108
}
109109
}
110110

@@ -407,7 +407,6 @@ public void testGetNextActionName() {
407407
assertInvalidAction("hot", AllocateAction.NAME, new String[] { RolloverAction.NAME });
408408
assertInvalidAction("hot", DeleteAction.NAME, new String[] { RolloverAction.NAME });
409409
assertInvalidAction("hot", ReadOnlyAction.NAME, new String[] { RolloverAction.NAME });
410-
assertInvalidAction("hot", ShrinkAction.NAME, new String[] { RolloverAction.NAME });
411410

412411
// Warm Phase
413412
assertNextActionName("warm", SetPriorityAction.NAME, UnfollowAction.NAME,

0 commit comments

Comments
 (0)