21
21
import org .elasticsearch .cluster .metadata .IndexMetadata ;
22
22
import org .elasticsearch .cluster .service .ClusterService ;
23
23
import org .elasticsearch .common .TriFunction ;
24
- import org .elasticsearch .common .bytes .BytesReference ;
24
+ import org .elasticsearch .common .breaker .CircuitBreaker ;
25
+ import org .elasticsearch .common .breaker .CircuitBreakingException ;
26
+ import org .elasticsearch .common .bytes .RecyclingBytesStreamOutput ;
25
27
import org .elasticsearch .common .collect .Tuple ;
26
28
import org .elasticsearch .common .io .stream .ByteBufferStreamInput ;
27
- import org .elasticsearch .common .io .stream .BytesStreamOutput ;
28
29
import org .elasticsearch .common .io .stream .NamedWriteableAwareStreamInput ;
29
30
import org .elasticsearch .common .io .stream .NamedWriteableRegistry ;
30
31
import org .elasticsearch .common .io .stream .StreamInput ;
31
32
import org .elasticsearch .common .io .stream .Writeable ;
33
+ import org .elasticsearch .common .lease .Releasable ;
32
34
import org .elasticsearch .common .settings .Settings ;
35
+ import org .elasticsearch .common .util .BigArrays ;
33
36
import org .elasticsearch .common .util .concurrent .ThreadContext ;
34
37
import org .elasticsearch .common .xcontent .XContentBuilder ;
35
38
import org .elasticsearch .common .xcontent .XContentType ;
36
39
import org .elasticsearch .indices .SystemIndexDescriptor ;
40
+ import org .elasticsearch .indices .breaker .CircuitBreakerService ;
37
41
import org .elasticsearch .search .fetch .subphase .FetchSourceContext ;
38
42
import org .elasticsearch .tasks .Task ;
39
43
import org .elasticsearch .tasks .TaskManager ;
@@ -180,16 +184,23 @@ public Authentication getAuthentication() {
180
184
public void createResponse (String docId ,
181
185
Map <String , String > headers ,
182
186
R response ,
183
- ActionListener <IndexResponse > listener ) throws IOException {
184
- Map <String , Object > source = new HashMap <>();
185
- source .put (HEADERS_FIELD , headers );
186
- source .put (EXPIRATION_TIME_FIELD , response .getExpirationTime ());
187
- source .put (RESULT_FIELD , encodeResponse (response ));
188
- IndexRequest indexRequest = new IndexRequest (index )
189
- .create (true )
190
- .id (docId )
191
- .source (source , XContentType .JSON );
192
- clientWithOrigin .index (indexRequest , listener );
187
+ CircuitBreakerService circuitBreakerService ,
188
+ ActionListener <IndexResponse > listener0 ) throws IOException {
189
+ AsyncResponseUpdateContext updateContext = new AsyncResponseUpdateContext (circuitBreakerService );
190
+ ActionListener <IndexResponse > listener = ActionListener .runAfter (listener0 , () -> updateContext .close ());
191
+ try {
192
+ Map <String , Object > source = new HashMap <>();
193
+ source .put (HEADERS_FIELD , headers );
194
+ source .put (EXPIRATION_TIME_FIELD , response .getExpirationTime ());
195
+ source .put (RESULT_FIELD , encodeResponse (response , updateContext ));
196
+ IndexRequest indexRequest = new IndexRequest (index )
197
+ .create (true )
198
+ .id (docId )
199
+ .source (source , XContentType .JSON );
200
+ clientWithOrigin .index (indexRequest , listener );
201
+ } catch (Exception e ) {
202
+ listener .onFailure (e );
203
+ }
193
204
}
194
205
195
206
/**
@@ -198,11 +209,14 @@ public void createResponse(String docId,
198
209
public void updateResponse (String docId ,
199
210
Map <String , List <String >> responseHeaders ,
200
211
R response ,
201
- ActionListener <UpdateResponse > listener ) {
212
+ CircuitBreakerService circuitBreakerService ,
213
+ ActionListener <UpdateResponse > listener0 ) {
214
+ AsyncResponseUpdateContext updateContext = new AsyncResponseUpdateContext (circuitBreakerService );
215
+ ActionListener <UpdateResponse > listener = ActionListener .runAfter (listener0 , () -> updateContext .close ());
202
216
try {
203
217
Map <String , Object > source = new HashMap <>();
204
218
source .put (RESPONSE_HEADERS_FIELD , responseHeaders );
205
- source .put (RESULT_FIELD , encodeResponse (response ));
219
+ source .put (RESULT_FIELD , encodeResponse (response , updateContext ));
206
220
UpdateRequest request = new UpdateRequest ()
207
221
.index (index )
208
222
.id (docId )
@@ -453,13 +467,27 @@ boolean ensureAuthenticatedUserIsSame(Map<String, String> originHeaders, Authent
453
467
}
454
468
455
469
/**
456
- * Encode the provided response in a binary form using base64 encoding.
470
+ * Encodes the provided response in a binary form using base64 encoding.
471
+ * Needs approximately up to 3.3X extra memory, where X is the original response size:
472
+ * - extra X bytes - for RecyclingBytesStreamOutput that encodes the response in an array of bytes,
473
+ * this memory allocation will be tracked automatically by BigArrays with circuitBreaker
474
+ * - up to X bytes – for converting bytes stream to bytes array
475
+ * - up to 1.3X bytes for encoded string, as Base64 adds around 33% overhead
476
+ * @throws CircuitBreakingException
457
477
*/
458
- String encodeResponse (R response ) throws IOException {
459
- try (BytesStreamOutput out = new BytesStreamOutput ()) {
478
+ String encodeResponse (R response , AsyncResponseUpdateContext updateContext ) throws IOException {
479
+ BigArrays bigArrays = new BigArrays (
480
+ null , updateContext .circuitBreakerService (), CircuitBreaker .REQUEST ).withCircuitBreaking ();
481
+ // using RecyclingBytesStreamOutput allows to supply BigArrays with a circuit breaker
482
+ try (RecyclingBytesStreamOutput out = new RecyclingBytesStreamOutput (new byte [0 ], bigArrays )) {
460
483
Version .writeVersion (Version .CURRENT , out );
461
484
response .writeTo (out );
462
- return Base64 .getEncoder ().encodeToString (BytesReference .toBytes (out .bytes ()));
485
+
486
+ // need to check from circuitBreaker if additional 2.3X size is available
487
+ long estimatedSize = Math .round (out .size () * 2.3 );
488
+ updateContext .addCircuitBreakerBytes (estimatedSize );
489
+
490
+ return Base64 .getEncoder ().encodeToString (out .toBytesRef ().bytes );
463
491
}
464
492
}
465
493
@@ -485,4 +513,32 @@ public static void restoreResponseHeadersContext(ThreadContext threadContext, Ma
485
513
}
486
514
}
487
515
}
516
+
517
+ /**
518
+ * A helper class for updating async search responses to track the memory usage
519
+ */
520
+ static class AsyncResponseUpdateContext implements Releasable {
521
+ private long circuitBreakerBytes = 0L ;
522
+ private CircuitBreakerService circuitBreakerService ;
523
+
524
+ AsyncResponseUpdateContext (CircuitBreakerService circuitBreakerService ) {
525
+ assert circuitBreakerService != null : "Circuit breaker service must be provided when storing async search response!" ;
526
+ this .circuitBreakerService = circuitBreakerService ;
527
+ }
528
+
529
+ public CircuitBreakerService circuitBreakerService () {
530
+ return circuitBreakerService ;
531
+ }
532
+
533
+ public void addCircuitBreakerBytes (long estimatedSize ) {
534
+ circuitBreakerService .getBreaker (CircuitBreaker .REQUEST )
535
+ .addEstimateBytesAndMaybeBreak (estimatedSize , "<storing_async_search_response>" );
536
+ circuitBreakerBytes += estimatedSize ;
537
+ }
538
+
539
+ @ Override
540
+ public void close () {
541
+ circuitBreakerService .getBreaker (CircuitBreaker .REQUEST ).addWithoutBreaking (-circuitBreakerBytes );
542
+ }
543
+ }
488
544
}
0 commit comments