26
26
import org .elasticsearch .action .support .AutoCreateIndex ;
27
27
import org .elasticsearch .action .support .HandledTransportAction ;
28
28
import org .elasticsearch .client .Client ;
29
+ import org .elasticsearch .cluster .ClusterState ;
30
+ import org .elasticsearch .cluster .metadata .AliasOrIndex ;
31
+ import org .elasticsearch .cluster .metadata .IndexMetaData ;
29
32
import org .elasticsearch .cluster .metadata .IndexNameExpressionResolver ;
30
33
import org .elasticsearch .cluster .service .ClusterService ;
31
34
import org .elasticsearch .common .UUIDs ;
32
35
import org .elasticsearch .common .collect .Tuple ;
33
36
import org .elasticsearch .common .inject .Inject ;
34
37
import org .elasticsearch .common .settings .Settings ;
35
38
import org .elasticsearch .common .util .concurrent .ThreadContext ;
39
+ import org .elasticsearch .common .util .set .Sets ;
36
40
import org .elasticsearch .common .xcontent .NamedXContentRegistry ;
37
41
import org .elasticsearch .persistent .PersistentTasksCustomMetaData ;
38
42
import org .elasticsearch .persistent .PersistentTasksService ;
41
45
import org .elasticsearch .threadpool .ThreadPool ;
42
46
import org .elasticsearch .transport .TransportService ;
43
47
48
+ import java .util .ArrayList ;
49
+ import java .util .Arrays ;
50
+ import java .util .Collections ;
51
+ import java .util .Comparator ;
44
52
import java .util .List ;
45
53
import java .util .Map ;
54
+ import java .util .Objects ;
55
+ import java .util .Set ;
56
+ import java .util .SortedMap ;
46
57
import java .util .function .Predicate ;
47
58
import java .util .stream .Collectors ;
59
+ import java .util .stream .Stream ;
48
60
49
61
public class TransportStartReindexTaskAction
50
62
extends HandledTransportAction <StartReindexTaskAction .Request , StartReindexTaskAction .Response > {
51
63
52
64
private final List <String > headersToInclude ;
53
65
private final ThreadPool threadPool ;
54
66
private final PersistentTasksService persistentTasksService ;
67
+ private final IndexNameExpressionResolver indexNameExpressionResolver ;
68
+ private final ClusterService clusterService ;
55
69
private final ReindexValidator reindexValidator ;
56
70
private final ReindexIndexClient reindexIndexClient ;
57
71
@@ -63,6 +77,8 @@ public TransportStartReindexTaskAction(Settings settings, Client client, Transpo
63
77
super (StartReindexTaskAction .NAME , transportService , actionFilters , StartReindexTaskAction .Request ::new );
64
78
this .headersToInclude = ReindexHeaders .REINDEX_INCLUDED_HEADERS .get (settings );
65
79
this .threadPool = threadPool ;
80
+ this .indexNameExpressionResolver = indexNameExpressionResolver ;
81
+ this .clusterService = clusterService ;
66
82
this .reindexValidator = new ReindexValidator (settings , clusterService , indexNameExpressionResolver , autoCreateIndex );
67
83
this .persistentTasksService = persistentTasksService ;
68
84
this .reindexIndexClient = new ReindexIndexClient (client , clusterService , xContentRegistry );
@@ -87,7 +103,7 @@ protected void doExecute(Task task, StartReindexTaskAction.Request request, Acti
87
103
88
104
// In the current implementation, we only need to store task results if we do not wait for completion
89
105
boolean storeTaskResult = request .getWaitForCompletion () == false ;
90
- ReindexTaskParams job = new ReindexTaskParams (storeTaskResult , included );
106
+ ReindexTaskParams job = new ReindexTaskParams (storeTaskResult , resolveIndexPatterns ( request . getReindexRequest ()), included );
91
107
92
108
ReindexTaskStateDoc reindexState = new ReindexTaskStateDoc (request .getReindexRequest ());
93
109
reindexIndexClient .createReindexTaskDoc (generatedId , reindexState , new ActionListener <>() {
@@ -212,4 +228,164 @@ private boolean isDone(ReindexPersistentTaskState state) {
212
228
return state != null && state .isDone ();
213
229
}
214
230
}
231
+
232
+ /**
233
+ * Resolve index patterns to ensure they do not start resolving differently during reindex failovers.
234
+ * Do not resolve aliases, since accessing the underlying indices is not semantically equivalent to accessing the alias.
235
+ * Within each index pattern, sort the resolved indices by create date, since this ensures that if we reindex from a pattern of indices,
236
+ * destination will receive oldest data first. This is in particular important if destination does rollover and it is time-based data.
237
+ *
238
+ * @return list of groups of indices/aliases that must be searched together.
239
+ */
240
+ private List <Set <String >> resolveIndexPatterns (ReindexRequest request ) {
241
+ return resolveIndexPatterns (request , clusterService .state (), indexNameExpressionResolver );
242
+ }
243
+
244
+ // visible for testing
245
+ static List <Set <String >> resolveIndexPatterns (ReindexRequest request , ClusterState clusterState ,
246
+ IndexNameExpressionResolver indexNameResolver ) {
247
+ if (request .getRemoteInfo () == null ) {
248
+ return resolveIndexPatterns (request .getSearchRequest ().indices (), clusterState , indexNameResolver );
249
+ } else {
250
+ return Collections .emptyList ();
251
+ }
252
+ }
253
+
254
+ private static List <Set <String >> resolveIndexPatterns (String [] indices , ClusterState clusterState ,
255
+ IndexNameExpressionResolver indexNameResolver ) {
256
+ Set <String > resolvedNames = indexNameResolver .resolveExpressions (clusterState , indices );
257
+
258
+ List <IndexGroup > groups = Arrays .stream (indices )
259
+ .flatMap (expression -> resolveSingleIndexExpression (expression , resolvedNames ::contains ,clusterState , indexNameResolver ))
260
+ .collect (Collectors .toList ());
261
+
262
+ return resolveGroups (groups ).stream ().map (IndexGroup ::newResolvedGroup ).collect (Collectors .toList ());
263
+ }
264
+
265
+ private static List <IndexGroup > resolveGroups (List <IndexGroup > groups ) {
266
+ List <IndexGroup > result = new ArrayList <>(groups );
267
+
268
+ // n^2, but OK since data volume is low.
269
+ // reverse order since we bubble data towards the lower index end.
270
+ for (int i = result .size () - 1 ; i >= 0 ; --i ) {
271
+ IndexGroup current = result .get (i );
272
+ for (int j = i - 1 ; current != null && j >= 0 ; --j ) {
273
+ IndexGroup earlier = result .get (j );
274
+ Tuple <IndexGroup , IndexGroup > collapsed = earlier .collapse (current );
275
+ result .set (j , collapsed .v1 ());
276
+ current = collapsed .v2 ();
277
+ }
278
+ result .set (i , current );
279
+ }
280
+
281
+ return result .stream ().filter (Objects ::nonNull ).collect (Collectors .toList ());
282
+ }
283
+
284
+ private static Stream <IndexGroup > resolveSingleIndexExpression (String expression , Predicate <String > predicate ,
285
+ ClusterState clusterState ,
286
+ IndexNameExpressionResolver indexNameExpressionResolver ) {
287
+ SortedMap <String , AliasOrIndex > lookup = clusterState .getMetaData ().getAliasAndIndexLookup ();
288
+ Comparator <AliasOrIndex > createDateIndexOrder = (i1 , i2 ) -> {
289
+ if (i1 .isAlias () && i2 .isAlias ()) {
290
+ return ((AliasOrIndex .Alias ) i1 ).getAliasName ().compareTo (((AliasOrIndex .Alias ) i2 ).getAliasName ());
291
+ }
292
+ if (i1 .isAlias () != i2 .isAlias ()) {
293
+ return Boolean .compare (i1 .isAlias (), i2 .isAlias ());
294
+ }
295
+
296
+ assert i1 .getIndices ().size () == 1 ;
297
+ assert i2 .getIndices ().size () == 1 ;
298
+ IndexMetaData indexMetaData1 = i1 .getIndices ().get (0 );
299
+ IndexMetaData indexMetaData2 = i2 .getIndices ().get (0 );
300
+ int compare = Long .compare (indexMetaData1 .getCreationDate (), indexMetaData2 .getCreationDate ());
301
+ return compare != 0 ? compare : indexMetaData1 .getIndex ().getName ().compareTo (indexMetaData2 .getIndex ().getName ());
302
+ };
303
+
304
+ return indexNameExpressionResolver .resolveExpressions (clusterState , expression ).stream ()
305
+ .filter (predicate ).map (lookup ::get ).sorted (createDateIndexOrder ).map (IndexGroup ::create );
306
+ }
307
+
308
+ /**
309
+ * Immutable group of indices and aliases.
310
+ */
311
+ private static class IndexGroup {
312
+ private final Set <String > indices ;
313
+ private final Set <String > allIndices ;
314
+ private final Set <AliasOrIndex .Alias > aliases ;
315
+ private final List <String > orderedIndices ;
316
+
317
+ private IndexGroup (List <String > indices , Set <AliasOrIndex .Alias > aliases ) {
318
+ orderedIndices = indices ;
319
+ this .indices = indices .stream ().collect (Collectors .toUnmodifiableSet ());
320
+ this .aliases = aliases ;
321
+ this .allIndices = Stream .concat (indices .stream (),
322
+ aliases .stream ().flatMap (aliasOrIndex -> aliasOrIndex .getIndices ().stream ())
323
+ .map (imd -> imd .getIndex ().getName ())
324
+ ).collect (Collectors .toUnmodifiableSet ());
325
+ }
326
+
327
+ private IndexGroup (IndexGroup group1 , IndexGroup group2 ) {
328
+ this (Stream .concat (group1 .orderedIndices .stream (), group2 .orderedIndices .stream ()).collect (Collectors .toList ()),
329
+ Stream .concat (group1 .aliases .stream (), group2 .aliases .stream ())
330
+ .collect (Collectors .toSet ()));
331
+ }
332
+
333
+ public static IndexGroup create (AliasOrIndex aliasOrIndex ) {
334
+ if (aliasOrIndex .isAlias ()) {
335
+ return new IndexGroup (Collections .emptyList (), Collections .singleton ((AliasOrIndex .Alias ) aliasOrIndex ));
336
+ } else {
337
+ return new IndexGroup (Collections .singletonList (aliasOrIndex .getIndices ().get (0 ).getIndex ().getName ()),
338
+ Collections .emptySet ());
339
+ }
340
+ }
341
+
342
+ private Tuple <IndexGroup , IndexGroup > collapse (IndexGroup other ) {
343
+ if (other == this ) {
344
+ return Tuple .tuple (this , this );
345
+ }
346
+
347
+ if (aliasOverlap (this .aliases , other .allIndices ) || aliasOverlap (other .aliases , this .allIndices )) {
348
+ return Tuple .tuple (new IndexGroup (this , other ), null );
349
+ }
350
+
351
+ Set <String > intersection = Sets .intersection (indices , other .indices );
352
+ assert intersection .isEmpty () == false || Sets .intersection (allIndices , other .allIndices ).isEmpty ();
353
+ assert intersection .isEmpty () || Sets .intersection (allIndices , other .allIndices ).isEmpty () == false ;
354
+ return Tuple .tuple (this .add (intersection , orderedIndices ), other .subtract (intersection ));
355
+ }
356
+
357
+ private IndexGroup add (Set <String > intersection , List <String > order ) {
358
+ if (intersection .isEmpty ()) {
359
+ return this ;
360
+ }
361
+
362
+ List <String > indices =
363
+ Stream .concat (this .orderedIndices .stream (), order .stream ().filter (intersection ::contains )).collect (Collectors .toList ());
364
+ return new IndexGroup (indices , aliases );
365
+ }
366
+
367
+ private IndexGroup subtract (Set <String > intersection ) {
368
+ if (intersection .isEmpty ()) {
369
+ return this ;
370
+ }
371
+
372
+ List <String > indices =
373
+ this .orderedIndices .stream ().filter (Predicate .not (intersection ::contains )).collect (Collectors .toList ());
374
+
375
+ if (indices .isEmpty ()) {
376
+ return null ;
377
+ }
378
+ return new IndexGroup (indices , aliases );
379
+ }
380
+
381
+ private static boolean aliasOverlap (Set <AliasOrIndex .Alias > aliases , Set <String > indices ) {
382
+ return aliases .stream ()
383
+ .flatMap (aliasOrIndex -> aliasOrIndex .getIndices ().stream ()).map (imd -> imd .getIndex ().getName ())
384
+ .anyMatch (indices ::contains );
385
+ }
386
+
387
+ public Set <String > newResolvedGroup () {
388
+ return Stream .concat (indices .stream (), aliases .stream ().map (AliasOrIndex .Alias ::getAliasName )).collect (Collectors .toSet ());
389
+ }
390
+ }
215
391
}
0 commit comments