8
8
9
9
package org .elasticsearch .action .bulk ;
10
10
11
+ import org .apache .logging .log4j .LogManager ;
12
+ import org .apache .logging .log4j .Logger ;
11
13
import org .apache .lucene .util .SparseFixedBitSet ;
12
14
import org .elasticsearch .action .ActionListener ;
13
15
import org .elasticsearch .action .DocWriteRequest ;
14
16
import org .elasticsearch .action .DocWriteResponse ;
17
+ import org .elasticsearch .action .index .IndexRequest ;
15
18
import org .elasticsearch .action .update .UpdateResponse ;
19
+ import org .elasticsearch .cluster .metadata .DataStream ;
16
20
import org .elasticsearch .cluster .metadata .IndexMetadata ;
17
21
import org .elasticsearch .common .util .set .Sets ;
18
22
import org .elasticsearch .core .Assertions ;
19
23
import org .elasticsearch .index .shard .ShardId ;
24
+ import org .elasticsearch .ingest .IngestService ;
20
25
26
+ import java .io .IOException ;
21
27
import java .util .ArrayList ;
22
28
import java .util .Iterator ;
23
29
import java .util .List ;
30
36
import static org .elasticsearch .index .seqno .SequenceNumbers .UNASSIGNED_PRIMARY_TERM ;
31
37
import static org .elasticsearch .index .seqno .SequenceNumbers .UNASSIGNED_SEQ_NO ;
32
38
39
+ /**
40
+ * Manages mutations to a bulk request that arise from the application of ingest pipelines. The modifier acts as an iterator over the
41
+ * documents of a bulk request, keeping a record of all dropped and failed write requests in the overall bulk operation.
42
+ * Once all pipelines have been applied, the modifier is used to create a new bulk request that will be used for executing the
43
+ * remaining writes. When this final bulk operation is completed, the modifier is used to combine the results with those from the
44
+ * ingest service to create the final bulk response.
45
+ */
33
46
final class BulkRequestModifier implements Iterator <DocWriteRequest <?>> {
34
47
48
+ private static final Logger logger = LogManager .getLogger (BulkRequestModifier .class );
49
+
35
50
private static final String DROPPED_OR_FAILED_ITEM_WITH_AUTO_GENERATED_ID = "auto-generated" ;
36
51
37
52
final BulkRequest bulkRequest ;
@@ -58,6 +73,13 @@ public boolean hasNext() {
58
73
return (currentSlot + 1 ) < bulkRequest .requests ().size ();
59
74
}
60
75
76
+ /**
77
+ * Creates a new bulk request containing all documents from the original bulk request that have not been marked as failed
78
+ * or dropped. Any failed or dropped documents are tracked as a side effect of this call so that they may be reflected in the
79
+ * final bulk response.
80
+ *
81
+ * @return A new bulk request without the write operations removed during any ingest pipeline executions.
82
+ */
61
83
BulkRequest getBulkRequest () {
62
84
if (itemResponses .isEmpty ()) {
63
85
return bulkRequest ;
@@ -80,6 +102,15 @@ BulkRequest getBulkRequest() {
80
102
}
81
103
}
82
104
105
+ /**
106
+ * If documents were dropped or failed in ingest, this method wraps the action listener that will be notified when the
107
+ * updated bulk operation is completed. The wrapped listener combines the dropped and failed document results from the ingest
108
+ * service with the results returned from running the remaining write operations.
109
+ *
110
+ * @param ingestTookInMillis Time elapsed for ingestion to be passed to final result.
111
+ * @param actionListener The action listener that expects the final bulk response.
112
+ * @return An action listener that combines ingest failure results with the results from writing the remaining documents.
113
+ */
83
114
ActionListener <BulkResponse > wrapActionListenerIfNeeded (long ingestTookInMillis , ActionListener <BulkResponse > actionListener ) {
84
115
if (itemResponses .isEmpty ()) {
85
116
return actionListener .map (
@@ -138,6 +169,11 @@ private void assertResponsesAreCorrect(BulkItemResponse[] bulkResponses, BulkIte
138
169
}
139
170
}
140
171
172
+ /**
173
+ * Mark the document at the given slot in the bulk request as having failed in the ingest service.
174
+ * @param slot the slot in the bulk request to mark as failed.
175
+ * @param e the failure encountered.
176
+ */
141
177
synchronized void markItemAsFailed (int slot , Exception e ) {
142
178
final DocWriteRequest <?> docWriteRequest = bulkRequest .requests ().get (slot );
143
179
final String id = Objects .requireNonNullElse (docWriteRequest .id (), DROPPED_OR_FAILED_ITEM_WITH_AUTO_GENERATED_ID );
@@ -150,6 +186,10 @@ synchronized void markItemAsFailed(int slot, Exception e) {
150
186
itemResponses .add (BulkItemResponse .failure (slot , docWriteRequest .opType (), failure ));
151
187
}
152
188
189
+ /**
190
+ * Mark the document at the given slot in the bulk request as having been dropped by the ingest service.
191
+ * @param slot the slot in the bulk request to mark as dropped.
192
+ */
153
193
synchronized void markItemAsDropped (int slot ) {
154
194
final DocWriteRequest <?> docWriteRequest = bulkRequest .requests ().get (slot );
155
195
final String id = Objects .requireNonNullElse (docWriteRequest .id (), DROPPED_OR_FAILED_ITEM_WITH_AUTO_GENERATED_ID );
@@ -164,4 +204,67 @@ synchronized void markItemAsDropped(int slot) {
164
204
);
165
205
itemResponses .add (BulkItemResponse .success (slot , docWriteRequest .opType (), dropped ));
166
206
}
207
+
208
+ /**
209
+ * Mark the document at the given slot in the bulk request as having failed in the ingest service. The document will be redirected
210
+ * to a data stream's failure store.
211
+ * @param slot the slot in the bulk request to redirect.
212
+ * @param targetIndexName the index that the document was targeting at the time of failure.
213
+ * @param e the failure encountered.
214
+ */
215
+ public void markItemForFailureStore (int slot , String targetIndexName , Exception e ) {
216
+ if (DataStream .isFailureStoreEnabled () == false ) {
217
+ // Assert false for development, but if we somehow find ourselves here, default to failure logic.
218
+ assert false
219
+ : "Attempting to route a failed write request type to a failure store but the failure store is not enabled! "
220
+ + "This should be guarded against in TransportBulkAction#shouldStoreFailure()" ;
221
+ markItemAsFailed (slot , e );
222
+ } else {
223
+ // We get the index write request to find the source of the failed document
224
+ IndexRequest indexRequest = TransportBulkAction .getIndexWriteRequest (bulkRequest .requests ().get (slot ));
225
+ if (indexRequest == null ) {
226
+ // This is unlikely to happen ever since only source oriented operations (index, create, upsert) are considered for
227
+ // ingest, but if it does happen, attempt to trip an assertion. If running in production, be defensive: Mark it failed
228
+ // as normal, and log the info for later debugging if needed.
229
+ assert false
230
+ : "Attempting to mark invalid write request type for failure store. Only IndexRequest or UpdateRequest allowed. "
231
+ + "type: ["
232
+ + bulkRequest .requests ().get (slot ).getClass ().getName ()
233
+ + "], index: ["
234
+ + targetIndexName
235
+ + "]" ;
236
+ markItemAsFailed (slot , e );
237
+ logger .debug (
238
+ () -> "Attempted to redirect an invalid write operation after ingest failure - type: ["
239
+ + bulkRequest .requests ().get (slot ).getClass ().getName ()
240
+ + "], index: ["
241
+ + targetIndexName
242
+ + "]"
243
+ );
244
+ } else {
245
+ try {
246
+ IndexRequest errorDocument = FailureStoreDocument .transformFailedRequest (indexRequest , e , targetIndexName );
247
+ // This is a fresh index request! We need to do some preprocessing on it. If we do not, when this is returned to
248
+ // the bulk action, the action will see that it hasn't been processed by ingest yet and attempt to ingest it again.
249
+ errorDocument .isPipelineResolved (true );
250
+ errorDocument .setPipeline (IngestService .NOOP_PIPELINE_NAME );
251
+ errorDocument .setFinalPipeline (IngestService .NOOP_PIPELINE_NAME );
252
+ bulkRequest .requests .set (slot , errorDocument );
253
+ } catch (IOException ioException ) {
254
+ // This is unlikely to happen because the conversion is so simple, but be defensive and attempt to report about it
255
+ // if we need the info later.
256
+ e .addSuppressed (ioException ); // Prefer to return the original exception to the end user instead of this new one.
257
+ logger .debug (
258
+ () -> "Encountered exception while attempting to redirect a failed ingest operation: index ["
259
+ + targetIndexName
260
+ + "], source: ["
261
+ + indexRequest .source ().utf8ToString ()
262
+ + "]" ,
263
+ ioException
264
+ );
265
+ markItemAsFailed (slot , e );
266
+ }
267
+ }
268
+ }
269
+ }
167
270
}
0 commit comments