Skip to content

Commit 6758c96

Browse files
committed
[ML] Wait for shards to initialize after creating ML internal indices
There have been a few test failures that are likely caused by tests performing actions that use ML indices immediately after the actions that create those ML indices. Currently this can result in attempts to search the newly created index before its shards have initialized. This change makes the method that creates the internal ML indices that have been affected by this problem (state and stats) wait for the shards to be initialized before returning. Fixes elastic#54887 Fixes elastic#55221 Fixes elastic#55807 Fixes elastic#57102 Fixes elastic#58841 Fixes elastic#59011
1 parent 34175b9 commit 6758c96

File tree

3 files changed

+35
-9
lines changed

3 files changed

+35
-9
lines changed

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

+35-7
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import org.apache.logging.log4j.Logger;
1010
import org.elasticsearch.ResourceAlreadyExistsException;
1111
import org.elasticsearch.action.ActionListener;
12+
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
13+
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
1214
import org.elasticsearch.action.admin.indices.alias.Alias;
1315
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
1416
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequestBuilder;
@@ -18,6 +20,7 @@
1820
import org.elasticsearch.action.support.IndicesOptions;
1921
import org.elasticsearch.action.support.master.AcknowledgedResponse;
2022
import org.elasticsearch.client.Client;
23+
import org.elasticsearch.client.Requests;
2124
import org.elasticsearch.cluster.ClusterState;
2225
import org.elasticsearch.cluster.metadata.IndexMetadata;
2326
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
@@ -69,13 +72,38 @@ private MlIndexAndAlias() {}
6972
* Adds an {@code alias} to that index if it was created,
7073
* or to the index with the highest suffix if the index did not have to be created.
7174
* The listener is notified with a {@code boolean} that informs whether the index or the alias were created.
75+
* If the index is created, the listener is not called until the index is ready to use via the supplied alias,
76+
* so that a method that receives a success response from this method can safely use the index immediately.
7277
*/
7378
public static void createIndexAndAliasIfNecessary(Client client,
7479
ClusterState clusterState,
7580
IndexNameExpressionResolver resolver,
7681
String indexPatternPrefix,
7782
String alias,
78-
ActionListener<Boolean> listener) {
83+
ActionListener<Boolean> finalListener) {
84+
85+
// If the index and alias were successfully created then wait for the shards of the index that the alias points to be ready
86+
ActionListener<Boolean> waitForShardsListener = ActionListener.wrap(
87+
created -> {
88+
if (created) {
89+
ClusterHealthRequest healthRequest = Requests.clusterHealthRequest(alias)
90+
.waitForNoRelocatingShards(true)
91+
.waitForNoInitializingShards(true);
92+
executeAsyncWithOrigin(
93+
client.threadPool().getThreadContext(),
94+
ML_ORIGIN,
95+
healthRequest,
96+
ActionListener.<ClusterHealthResponse>wrap(
97+
response -> finalListener.onResponse(response.isTimedOut() == false),
98+
finalListener::onFailure),
99+
(request, listener) -> client.admin().cluster().health(request, listener)
100+
);
101+
} else {
102+
finalListener.onResponse(false);
103+
}
104+
},
105+
finalListener::onFailure
106+
);
79107

80108
String legacyIndexWithoutSuffix = indexPatternPrefix;
81109
String indexPattern = indexPatternPrefix + "*";
@@ -89,15 +117,15 @@ public static void createIndexAndAliasIfNecessary(Client client,
89117

90118
if (concreteIndexNames.length == 0) {
91119
if (indexPointedByCurrentWriteAlias.isEmpty()) {
92-
createFirstConcreteIndex(client, firstConcreteIndex, alias, true, listener);
120+
createFirstConcreteIndex(client, firstConcreteIndex, alias, true, waitForShardsListener);
93121
return;
94122
}
95123
logger.error(
96124
"There are no indices matching '{}' pattern but '{}' alias points at [{}]. This should never happen.",
97125
indexPattern, alias, indexPointedByCurrentWriteAlias.get());
98126
} else if (concreteIndexNames.length == 1 && concreteIndexNames[0].equals(legacyIndexWithoutSuffix)) {
99127
if (indexPointedByCurrentWriteAlias.isEmpty()) {
100-
createFirstConcreteIndex(client, firstConcreteIndex, alias, true, listener);
128+
createFirstConcreteIndex(client, firstConcreteIndex, alias, true, waitForShardsListener);
101129
return;
102130
}
103131
if (indexPointedByCurrentWriteAlias.get().getIndex().getName().equals(legacyIndexWithoutSuffix)) {
@@ -107,8 +135,8 @@ public static void createIndexAndAliasIfNecessary(Client client,
107135
alias,
108136
false,
109137
ActionListener.wrap(
110-
unused -> updateWriteAlias(client, alias, legacyIndexWithoutSuffix, firstConcreteIndex, listener),
111-
listener::onFailure)
138+
unused -> updateWriteAlias(client, alias, legacyIndexWithoutSuffix, firstConcreteIndex, waitForShardsListener),
139+
finalListener::onFailure)
112140
);
113141
return;
114142
}
@@ -119,12 +147,12 @@ public static void createIndexAndAliasIfNecessary(Client client,
119147
if (indexPointedByCurrentWriteAlias.isEmpty()) {
120148
assert concreteIndexNames.length > 0;
121149
String latestConcreteIndexName = Arrays.stream(concreteIndexNames).max(INDEX_NAME_COMPARATOR).get();
122-
updateWriteAlias(client, alias, null, latestConcreteIndexName, listener);
150+
updateWriteAlias(client, alias, null, latestConcreteIndexName, finalListener);
123151
return;
124152
}
125153
}
126154
// If the alias is set, there is nothing more to do.
127-
listener.onResponse(false);
155+
finalListener.onResponse(false);
128156
}
129157

130158
private static void createFirstConcreteIndex(Client client,

x-pack/plugin/ml/qa/native-multi-node-tests/src/test/java/org/elasticsearch/xpack/ml/integration/DeleteExpiredDataIT.java

-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,6 @@ public void testDeleteExpiredDataActionDeletesEmptyStateIndices() throws Excepti
137137
is(arrayContaining(".ml-state-000001", ".ml-state-000005", ".ml-state-000007")));
138138
}
139139

140-
@AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/57102")
141140
public void testDeleteExpiredDataWithStandardThrottle() throws Exception {
142141
testExpiredDeletion(-1.0f, 100);
143142
}

x-pack/plugin/ml/qa/native-multi-node-tests/src/test/java/org/elasticsearch/xpack/ml/integration/RegressionIT.java

-1
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,6 @@ public void testStopAndRestart() throws Exception {
297297
assertMlResultsFieldMappings(destIndex, predictedClassField, "double");
298298
}
299299

300-
@AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/55807")
301300
public void testTwoJobsWithSameRandomizeSeedUseSameTrainingSet() throws Exception {
302301
String sourceIndex = "regression_two_jobs_with_same_randomize_seed_source";
303302
indexData(sourceIndex, 100, 0);

0 commit comments

Comments
 (0)