Skip to content

Commit 3231592

Browse files
authored
[ML] Automatically rollover legacy .ml-anomalies indices (#120885)
Rollover legacy v87 .ml-anomalies indices and update the job aliases. Anomaly detection requires read + write indices, this rollover ensures AD will continue to work after upgrading to 9
1 parent 41ace62 commit 3231592

File tree

12 files changed

+656
-38
lines changed

12 files changed

+656
-38
lines changed

docs/changelog/120885.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 120885
2+
summary: Automatically rollover legacy .ml-anomalies indices
3+
area: Machine Learning
4+
type: upgrade
5+
issues: []

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/persistence/AnomalyDetectorsIndex.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ public static String jobResultsIndexPrefix() {
3737
return AnomalyDetectorsIndexFields.RESULTS_INDEX_PREFIX;
3838
}
3939

40+
public static String jobResultsIndexPattern() {
41+
return AnomalyDetectorsIndexFields.RESULTS_INDEX_PREFIX + "*";
42+
}
43+
4044
/**
4145
* The name of the alias pointing to the indices where the job's results are stored
4246
* @param jobId Job Id
@@ -46,15 +50,26 @@ public static String jobResultsAliasedName(String jobId) {
4650
return AnomalyDetectorsIndexFields.RESULTS_INDEX_PREFIX + jobId;
4751
}
4852

53+
/**
54+
* Extract the job Id from the alias name.
55+
* If not an results index alias null is returned
56+
* @param jobResultsAliasedName The alias
57+
* @return The job Id
58+
*/
59+
public static String jobIdFromAlias(String jobResultsAliasedName) {
60+
if (jobResultsAliasedName.length() < AnomalyDetectorsIndexFields.RESULTS_INDEX_PREFIX.length()) {
61+
return null;
62+
}
63+
return jobResultsAliasedName.substring(AnomalyDetectorsIndexFields.RESULTS_INDEX_PREFIX.length());
64+
}
65+
4966
/**
5067
* The name of the alias pointing to the write index for a job
5168
* @param jobId Job Id
5269
* @return The write alias
5370
*/
5471
public static String resultsWriteAlias(String jobId) {
55-
// ".write" rather than simply "write" to avoid the danger of clashing
56-
// with the read alias of a job whose name begins with "write-"
57-
return AnomalyDetectorsIndexFields.RESULTS_INDEX_PREFIX + ".write-" + jobId;
72+
return AnomalyDetectorsIndexFields.RESULTS_INDEX_WRITE_PREFIX + jobId;
5873
}
5974

6075
/**

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/persistence/AnomalyDetectorsIndexFields.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ public final class AnomalyDetectorsIndexFields {
1111
public static final String STATE_INDEX_PREFIX = ".ml-state";
1212

1313
public static final String RESULTS_INDEX_PREFIX = ".ml-anomalies-";
14+
// ".write" rather than simply "write" to avoid the danger of clashing
15+
// with the read alias of a job whose name begins with "write-"
16+
public static final String RESULTS_INDEX_WRITE_PREFIX = RESULTS_INDEX_PREFIX + ".write-";
1417
public static final String RESULTS_INDEX_DEFAULT = "shared";
1518

1619
private AnomalyDetectorsIndexFields() {}

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/utils/MlIndexAndAlias.java

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import org.elasticsearch.core.Nullable;
3131
import org.elasticsearch.core.TimeValue;
3232
import org.elasticsearch.index.Index;
33+
import org.elasticsearch.index.IndexVersion;
34+
import org.elasticsearch.index.IndexVersions;
3335
import org.elasticsearch.indices.SystemIndexDescriptor;
3436
import org.elasticsearch.xcontent.XContentParserConfiguration;
3537
import org.elasticsearch.xcontent.json.JsonXContent;
@@ -63,27 +65,24 @@ public final class MlIndexAndAlias {
6365
*/
6466
public static final String BWC_MAPPINGS_VERSION = "8.11.0";
6567

66-
private static final Logger logger = LogManager.getLogger(MlIndexAndAlias.class);
68+
public static final String FIRST_INDEX_SIX_DIGIT_SUFFIX = "-000001";
6769

68-
static final Comparator<String> INDEX_NAME_COMPARATOR = new Comparator<>() {
69-
70-
private final Predicate<String> HAS_SIX_DIGIT_SUFFIX = Pattern.compile("\\d{6}").asMatchPredicate();
71-
72-
@Override
73-
public int compare(String index1, String index2) {
74-
String[] index1Parts = index1.split("-");
75-
String index1Suffix = index1Parts[index1Parts.length - 1];
76-
boolean index1HasSixDigitsSuffix = HAS_SIX_DIGIT_SUFFIX.test(index1Suffix);
77-
String[] index2Parts = index2.split("-");
78-
String index2Suffix = index2Parts[index2Parts.length - 1];
79-
boolean index2HasSixDigitsSuffix = HAS_SIX_DIGIT_SUFFIX.test(index2Suffix);
80-
if (index1HasSixDigitsSuffix && index2HasSixDigitsSuffix) {
81-
return index1Suffix.compareTo(index2Suffix);
82-
} else if (index1HasSixDigitsSuffix != index2HasSixDigitsSuffix) {
83-
return Boolean.compare(index1HasSixDigitsSuffix, index2HasSixDigitsSuffix);
84-
} else {
85-
return index1.compareTo(index2);
86-
}
70+
private static final Logger logger = LogManager.getLogger(MlIndexAndAlias.class);
71+
private static final Predicate<String> HAS_SIX_DIGIT_SUFFIX = Pattern.compile("\\d{6}").asMatchPredicate();
72+
73+
static final Comparator<String> INDEX_NAME_COMPARATOR = (index1, index2) -> {
74+
String[] index1Parts = index1.split("-");
75+
String index1Suffix = index1Parts[index1Parts.length - 1];
76+
boolean index1HasSixDigitsSuffix = HAS_SIX_DIGIT_SUFFIX.test(index1Suffix);
77+
String[] index2Parts = index2.split("-");
78+
String index2Suffix = index2Parts[index2Parts.length - 1];
79+
boolean index2HasSixDigitsSuffix = HAS_SIX_DIGIT_SUFFIX.test(index2Suffix);
80+
if (index1HasSixDigitsSuffix && index2HasSixDigitsSuffix) {
81+
return index1Suffix.compareTo(index2Suffix);
82+
} else if (index1HasSixDigitsSuffix != index2HasSixDigitsSuffix) {
83+
return Boolean.compare(index1HasSixDigitsSuffix, index2HasSixDigitsSuffix);
84+
} else {
85+
return index1.compareTo(index2);
8786
}
8887
};
8988

@@ -124,7 +123,7 @@ public static void createIndexAndAliasIfNecessary(
124123
String legacyIndexWithoutSuffix = indexPatternPrefix;
125124
String indexPattern = indexPatternPrefix + "*";
126125
// The initial index name must be suitable for rollover functionality.
127-
String firstConcreteIndex = indexPatternPrefix + "-000001";
126+
String firstConcreteIndex = indexPatternPrefix + FIRST_INDEX_SIX_DIGIT_SUFFIX;
128127
String[] concreteIndexNames = resolver.concreteIndexNames(clusterState, IndicesOptions.lenientExpandHidden(), indexPattern);
129128
Optional<String> indexPointedByCurrentWriteAlias = clusterState.getMetadata().hasAlias(alias)
130129
? clusterState.getMetadata().getIndicesLookup().get(alias).getIndices().stream().map(Index::getName).findFirst()
@@ -379,6 +378,10 @@ public static boolean hasIndexTemplate(ClusterState state, String templateName)
379378
return state.getMetadata().templatesV2().containsKey(templateName);
380379
}
381380

381+
public static boolean has6DigitSuffix(String indexName) {
382+
return HAS_SIX_DIGIT_SUFFIX.test(indexName);
383+
}
384+
382385
/**
383386
* Returns the latest index. Latest is the index with the highest
384387
* 6 digit suffix.
@@ -390,4 +393,11 @@ public static String latestIndex(String[] concreteIndices) {
390393
? concreteIndices[0]
391394
: Arrays.stream(concreteIndices).max(MlIndexAndAlias.INDEX_NAME_COMPARATOR).get();
392395
}
396+
397+
/**
398+
* True if the version is read *and* write compatible not just read only compatible
399+
*/
400+
public static boolean indexIsReadWriteCompatibleInV9(IndexVersion version) {
401+
return version.onOrAfter(IndexVersions.V_8_0_0);
402+
}
393403
}

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/utils/MlIndexAndAliasTests.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.elasticsearch.common.util.concurrent.ThreadContext;
3434
import org.elasticsearch.core.TimeValue;
3535
import org.elasticsearch.index.IndexVersion;
36+
import org.elasticsearch.index.IndexVersions;
3637
import org.elasticsearch.indices.TestIndexNameExpressionResolver;
3738
import org.elasticsearch.test.ESTestCase;
3839
import org.elasticsearch.threadpool.ThreadPool;
@@ -364,8 +365,20 @@ public void testIndexNameComparator() {
364365
}
365366

366367
public void testLatestIndex() {
367-
var names = new String[] { "index-000001", "index-000002", "index-000003" };
368-
assertThat(MlIndexAndAlias.latestIndex(names), equalTo("index-000003"));
368+
{
369+
var names = new String[] { "index-000001", "index-000002", "index-000003" };
370+
assertThat(MlIndexAndAlias.latestIndex(names), equalTo("index-000003"));
371+
}
372+
{
373+
var names = new String[] { "index", "index-000001", "index-000002" };
374+
assertThat(MlIndexAndAlias.latestIndex(names), equalTo("index-000002"));
375+
}
376+
}
377+
378+
public void testIndexIsReadWriteCompatibleInV9() {
379+
assertTrue(MlIndexAndAlias.indexIsReadWriteCompatibleInV9(IndexVersion.current()));
380+
assertTrue(MlIndexAndAlias.indexIsReadWriteCompatibleInV9(IndexVersions.V_8_0_0));
381+
assertFalse(MlIndexAndAlias.indexIsReadWriteCompatibleInV9(IndexVersions.V_7_17_0));
369382
}
370383

371384
private void createIndexAndAliasIfNecessary(ClusterState clusterState) {

x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1239,7 +1239,8 @@ public Collection<?> createComponents(PluginServices services) {
12391239
),
12401240
indexNameExpressionResolver,
12411241
client
1242-
)
1242+
),
1243+
new MlAnomaliesIndexUpdate(indexNameExpressionResolver, client)
12431244
)
12441245
);
12451246
clusterService.addListener(mlAutoUpdateService);

0 commit comments

Comments
 (0)