7
7
*/
8
8
package org .elasticsearch .action .admin .indices .rollover ;
9
9
10
+ import org .apache .logging .log4j .LogManager ;
11
+ import org .apache .logging .log4j .Logger ;
10
12
import org .elasticsearch .action .ActionListener ;
11
13
import org .elasticsearch .action .ActionType ;
12
14
import org .elasticsearch .action .datastreams .autosharding .DataStreamAutoShardingService ;
13
15
import org .elasticsearch .action .support .ActionFilters ;
16
+ import org .elasticsearch .action .support .ActiveShardsObserver ;
14
17
import org .elasticsearch .client .internal .Client ;
15
18
import org .elasticsearch .cluster .ClusterState ;
19
+ import org .elasticsearch .cluster .ClusterStateTaskExecutor ;
20
+ import org .elasticsearch .cluster .ClusterStateTaskListener ;
16
21
import org .elasticsearch .cluster .metadata .DataStream ;
17
22
import org .elasticsearch .cluster .metadata .IndexNameExpressionResolver ;
18
23
import org .elasticsearch .cluster .metadata .Metadata ;
19
24
import org .elasticsearch .cluster .metadata .MetadataDataStreamsService ;
20
25
import org .elasticsearch .cluster .routing .allocation .AllocationService ;
26
+ import org .elasticsearch .cluster .routing .allocation .allocator .AllocationActionMultiListener ;
21
27
import org .elasticsearch .cluster .service .ClusterService ;
28
+ import org .elasticsearch .cluster .service .MasterServiceTaskQueue ;
29
+ import org .elasticsearch .common .Priority ;
30
+ import org .elasticsearch .common .Strings ;
31
+ import org .elasticsearch .common .collect .Iterators ;
22
32
import org .elasticsearch .common .inject .Inject ;
23
33
import org .elasticsearch .features .NodeFeature ;
24
34
import org .elasticsearch .tasks .CancellableTask ;
25
35
import org .elasticsearch .tasks .Task ;
26
36
import org .elasticsearch .threadpool .ThreadPool ;
27
37
import org .elasticsearch .transport .TransportService ;
28
38
39
+ import java .time .Instant ;
40
+ import java .util .ArrayList ;
41
+ import java .util .HashMap ;
42
+ import java .util .List ;
29
43
import java .util .Map ;
44
+ import java .util .function .Consumer ;
30
45
31
46
/**
32
47
* API that lazily rolls over a data stream that has the flag {@link DataStream#rolloverOnWrite()} enabled. These requests always
33
48
* originate from requests that write into the data stream.
34
49
*/
35
50
public final class LazyRolloverAction extends ActionType <RolloverResponse > {
36
51
52
+ private static final Logger logger = LogManager .getLogger (LazyRolloverAction .class );
53
+
37
54
public static final NodeFeature DATA_STREAM_LAZY_ROLLOVER = new NodeFeature ("data_stream.rollover.lazy" );
38
55
39
56
public static final LazyRolloverAction INSTANCE = new LazyRolloverAction ();
@@ -50,6 +67,8 @@ public String name() {
50
67
51
68
public static final class TransportLazyRolloverAction extends TransportRolloverAction {
52
69
70
+ private final MasterServiceTaskQueue <LazyRolloverTask > lazyRolloverTaskQueue ;
71
+
53
72
@ Inject
54
73
public TransportLazyRolloverAction (
55
74
TransportService transportService ,
@@ -76,6 +95,11 @@ public TransportLazyRolloverAction(
76
95
metadataDataStreamsService ,
77
96
dataStreamAutoShardingService
78
97
);
98
+ this .lazyRolloverTaskQueue = clusterService .createTaskQueue (
99
+ "lazy-rollover" ,
100
+ Priority .NORMAL ,
101
+ new LazyRolloverExecutor (clusterService , allocationService , rolloverService , threadPool )
102
+ );
79
103
}
80
104
81
105
@ Override
@@ -93,6 +117,12 @@ protected void masterOperation(
93
117
: "The auto rollover action does not expect any other parameters in the request apart from the data stream name" ;
94
118
95
119
Metadata metadata = clusterState .metadata ();
120
+ DataStream dataStream = metadata .dataStreams ().get (rolloverRequest .getRolloverTarget ());
121
+ // Skip submitting the task if we detect that the lazy rollover has been already executed.
122
+ if (dataStream .rolloverOnWrite () == false ) {
123
+ listener .onResponse (noopLazyRolloverResponse (dataStream ));
124
+ return ;
125
+ }
96
126
// We evaluate the names of the source index as well as what our newly created index would be.
97
127
final MetadataRolloverService .NameResolution trialRolloverNames = MetadataRolloverService .resolveRolloverNames (
98
128
clusterState ,
@@ -107,28 +137,164 @@ protected void masterOperation(
107
137
108
138
assert metadata .dataStreams ().containsKey (rolloverRequest .getRolloverTarget ()) : "Auto-rollover applies only to data streams" ;
109
139
110
- final RolloverResponse trialRolloverResponse = new RolloverResponse (
111
- trialSourceIndexName ,
112
- trialRolloverIndexName ,
113
- Map .of (),
114
- false ,
115
- false ,
116
- false ,
117
- false ,
118
- false
119
- );
120
-
121
140
String source = "lazy_rollover source [" + trialSourceIndexName + "] to target [" + trialRolloverIndexName + "]" ;
122
141
// We create a new rollover request to ensure that it doesn't contain any other parameters apart from the data stream name
123
142
// This will provide a more resilient user experience
124
- RolloverTask rolloverTask = new RolloverTask (
125
- new RolloverRequest (rolloverRequest .getRolloverTarget (), null ),
143
+ var newRolloverRequest = new RolloverRequest (rolloverRequest .getRolloverTarget (), null );
144
+ newRolloverRequest .setIndicesOptions (rolloverRequest .indicesOptions ());
145
+ LazyRolloverTask rolloverTask = new LazyRolloverTask (newRolloverRequest , listener );
146
+ lazyRolloverTaskQueue .submitTask (source , rolloverTask , rolloverRequest .masterNodeTimeout ());
147
+ }
148
+ }
149
+
150
+ /**
151
+ * A lazy rollover task holds the rollover request and the listener.
152
+ */
153
+ record LazyRolloverTask (RolloverRequest rolloverRequest , ActionListener <RolloverResponse > listener )
154
+ implements
155
+ ClusterStateTaskListener {
156
+
157
+ @ Override
158
+ public void onFailure (Exception e ) {
159
+ listener .onFailure (e );
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Performs a lazy rollover when required and notifies the listener. Due to the nature of the lazy rollover we are able
165
+ * to perform certain optimisations like identifying duplicate requests and executing them once. This is an optimisation
166
+ * that can work since we do not take into consideration any stats or auto-sharding conditions here.
167
+ */
168
+ record LazyRolloverExecutor (
169
+ ClusterService clusterService ,
170
+ AllocationService allocationService ,
171
+ MetadataRolloverService rolloverService ,
172
+ ThreadPool threadPool
173
+ ) implements ClusterStateTaskExecutor <LazyRolloverTask > {
174
+
175
+ @ Override
176
+ public ClusterState execute (BatchExecutionContext <LazyRolloverTask > batchExecutionContext ) {
177
+ final var listener = new AllocationActionMultiListener <RolloverResponse >(threadPool .getThreadContext ());
178
+ final var results = new ArrayList <MetadataRolloverService .RolloverResult >(batchExecutionContext .taskContexts ().size ());
179
+ var state = batchExecutionContext .initialState ();
180
+ Map <RolloverRequest , List <TaskContext <LazyRolloverTask >>> groupedRequests = new HashMap <>();
181
+ for (final var taskContext : batchExecutionContext .taskContexts ()) {
182
+ groupedRequests .computeIfAbsent (taskContext .getTask ().rolloverRequest (), ignored -> new ArrayList <>()).add (taskContext );
183
+ }
184
+ for (final var entry : groupedRequests .entrySet ()) {
185
+ List <TaskContext <LazyRolloverTask >> rolloverTaskContexts = entry .getValue ();
186
+ try {
187
+ RolloverRequest rolloverRequest = entry .getKey ();
188
+ state = executeTask (state , rolloverRequest , results , rolloverTaskContexts , listener );
189
+ } catch (Exception e ) {
190
+ rolloverTaskContexts .forEach (taskContext -> taskContext .onFailure (e ));
191
+ } finally {
192
+ rolloverTaskContexts .forEach (taskContext -> taskContext .captureResponseHeaders ().close ());
193
+ }
194
+ }
195
+
196
+ if (state != batchExecutionContext .initialState ()) {
197
+ var reason = new StringBuilder ();
198
+ Strings .collectionToDelimitedStringWithLimit (
199
+ (Iterable <String >) () -> Iterators .map (results .iterator (), t -> t .sourceIndexName () + "->" + t .rolloverIndexName ()),
200
+ "," ,
201
+ "lazy bulk rollover [" ,
202
+ "]" ,
203
+ 1024 ,
204
+ reason
205
+ );
206
+ try (var ignored = batchExecutionContext .dropHeadersContext ()) {
207
+ state = allocationService .reroute (state , reason .toString (), listener .reroute ());
208
+ }
209
+ } else {
210
+ listener .noRerouteNeeded ();
211
+ }
212
+ return state ;
213
+ }
214
+
215
+ public ClusterState executeTask (
216
+ ClusterState currentState ,
217
+ RolloverRequest rolloverRequest ,
218
+ List <MetadataRolloverService .RolloverResult > results ,
219
+ List <TaskContext <LazyRolloverTask >> rolloverTaskContexts ,
220
+ AllocationActionMultiListener <RolloverResponse > allocationActionMultiListener
221
+ ) throws Exception {
222
+
223
+ // If the data stream has been rolled over since it was marked for lazy rollover, this operation is a noop
224
+ final DataStream dataStream = currentState .metadata ().dataStreams ().get (rolloverRequest .getRolloverTarget ());
225
+ assert dataStream != null ;
226
+
227
+ if (dataStream .rolloverOnWrite () == false ) {
228
+ var noopResponse = noopLazyRolloverResponse (dataStream );
229
+ notifyAllListeners (rolloverTaskContexts , context -> context .getTask ().listener .onResponse (noopResponse ));
230
+ return currentState ;
231
+ }
232
+
233
+ // Perform the actual rollover
234
+ final var rolloverResult = rolloverService .rolloverClusterState (
235
+ currentState ,
236
+ rolloverRequest .getRolloverTarget (),
237
+ rolloverRequest .getNewIndexName (),
238
+ rolloverRequest .getCreateIndexRequest (),
239
+ List .of (),
240
+ Instant .now (),
241
+ false ,
242
+ false ,
126
243
null ,
127
- trialRolloverResponse ,
128
244
null ,
129
- listener
245
+ rolloverRequest . targetsFailureStore ()
130
246
);
131
- submitRolloverTask (rolloverRequest , source , rolloverTask );
247
+ results .add (rolloverResult );
248
+ logger .trace ("lazy rollover result [{}]" , rolloverResult );
249
+
250
+ final var rolloverIndexName = rolloverResult .rolloverIndexName ();
251
+ final var sourceIndexName = rolloverResult .sourceIndexName ();
252
+
253
+ final var waitForActiveShardsTimeout = rolloverRequest .masterNodeTimeout ().millis () < 0
254
+ ? null
255
+ : rolloverRequest .masterNodeTimeout ();
256
+
257
+ notifyAllListeners (rolloverTaskContexts , context -> {
258
+ // Now assuming we have a new state and the name of the rolled over index, we need to wait for the configured number of
259
+ // active shards, as well as return the names of the indices that were rolled/created
260
+ ActiveShardsObserver .waitForActiveShards (
261
+ clusterService ,
262
+ new String [] { rolloverIndexName },
263
+ rolloverRequest .getCreateIndexRequest ().waitForActiveShards (),
264
+ waitForActiveShardsTimeout ,
265
+ allocationActionMultiListener .delay (context .getTask ().listener ())
266
+ .map (
267
+ isShardsAcknowledged -> new RolloverResponse (
268
+ // Note that we use the actual rollover result for these, because even though we're single threaded,
269
+ // it's possible for the rollover names generated before the actual rollover to be different due to
270
+ // things like date resolution
271
+ sourceIndexName ,
272
+ rolloverIndexName ,
273
+ Map .of (),
274
+ false ,
275
+ true ,
276
+ true ,
277
+ isShardsAcknowledged ,
278
+ false
279
+ )
280
+ )
281
+ );
282
+ });
283
+
284
+ // Return the new rollover cluster state, which includes the changes that create the new index
285
+ return rolloverResult .clusterState ();
132
286
}
133
287
}
288
+
289
+ private static void notifyAllListeners (
290
+ List <ClusterStateTaskExecutor .TaskContext <LazyRolloverTask >> taskContexts ,
291
+ Consumer <ClusterStateTaskExecutor .TaskContext <LazyRolloverTask >> onPublicationSuccess
292
+ ) {
293
+ taskContexts .forEach (context -> context .success (() -> onPublicationSuccess .accept (context )));
294
+ }
295
+
296
+ private static RolloverResponse noopLazyRolloverResponse (DataStream dataStream ) {
297
+ String latestWriteIndex = dataStream .getWriteIndex ().getName ();
298
+ return new RolloverResponse (latestWriteIndex , latestWriteIndex , Map .of (), false , false , true , true , false );
299
+ }
134
300
}
0 commit comments