2424import org .elasticsearch .common .lucene .search .TopDocsAndMaxScore ;
2525import org .elasticsearch .common .util .concurrent .AbstractRunnable ;
2626import org .elasticsearch .core .Nullable ;
27- import org .elasticsearch .core .Releasable ;
28- import org .elasticsearch .core .Releasables ;
2927import org .elasticsearch .core .Tuple ;
3028import org .elasticsearch .search .SearchPhaseResult ;
3129import org .elasticsearch .search .SearchService ;
3230import org .elasticsearch .search .SearchShardTarget ;
3331import org .elasticsearch .search .aggregations .AggregationReduceContext ;
32+ import org .elasticsearch .search .aggregations .AggregatorsReducer ;
3433import org .elasticsearch .search .aggregations .InternalAggregations ;
3534import org .elasticsearch .search .builder .SearchSourceBuilder ;
3635import org .elasticsearch .search .query .QuerySearchResult ;
@@ -246,7 +245,10 @@ public SearchPhaseController.ReducedQueryPhase reduce() throws Exception {
246245 if (aggsList != null ) {
247246 // Add an estimate of the final reduce size
248247 breakerSize = addEstimateAndMaybeBreak (estimateRamBytesUsedForReduce (breakerSize ));
249- aggs = aggregate (buffer .iterator (), new Iterator <>() {
248+ var reduceContext = performFinalReduce
249+ ? aggReduceContextBuilder .forFinalReduction ()
250+ : aggReduceContextBuilder .forPartialReduction ();
251+ aggs = InternalAggregations .maybeExecuteFinalReduce (reduceContext , aggregate (buffer .iterator (), new Iterator <>() {
250252 @ Override
251253 public boolean hasNext () {
252254 return aggsList .isEmpty () == false ;
@@ -256,10 +258,7 @@ public boolean hasNext() {
256258 public InternalAggregations next () {
257259 return aggsList .pollFirst ();
258260 }
259- },
260- resultSize ,
261- performFinalReduce ? aggReduceContextBuilder .forFinalReduction () : aggReduceContextBuilder .forPartialReduction ()
262- );
261+ }, resultSize , reduceContext ));
263262 } else {
264263 aggs = null ;
265264 }
@@ -391,39 +390,59 @@ private static InternalAggregations aggregate(
391390 int resultSetSize ,
392391 AggregationReduceContext reduceContext
393392 ) {
394- interface ReleasableIterator extends Iterator <InternalAggregations >, Releasable {}
395- try (var aggsIter = new ReleasableIterator () {
396-
397- private Releasable toRelease ;
398-
399- @ Override
400- public void close () {
401- Releasables .close (toRelease );
393+ if (resultSetSize == 1 ) {
394+ if (partialResults .hasNext ()) {
395+ return InternalAggregations .reduce (partialResults .next (), reduceContext );
402396 }
403-
404- @ Override
405- public boolean hasNext () {
406- return toConsume .hasNext ();
397+ try (var delayable = toConsume .next ().consumeAggs ()) {
398+ return InternalAggregations .reduce (delayable .expand (), reduceContext );
407399 }
408-
409- @ Override
410- public InternalAggregations next () {
411- var res = toConsume .next ().consumeAggs ();
412- Releasables .close (toRelease );
413- toRelease = res ;
414- return res .expand ();
400+ }
401+ try {
402+ // general case
403+ if (partialResults .hasNext ()) {
404+ return consumeAggResults (partialResults , toConsume , createReducer (resultSetSize , reduceContext , partialResults .next ()));
415405 }
416- }) {
417- return InternalAggregations .topLevelReduce (
418- partialResults .hasNext () ? Iterators .concat (partialResults , aggsIter ) : aggsIter ,
419- resultSetSize ,
420- reduceContext
421- );
406+ AggregatorsReducer reducer ;
407+ try (var delayable = toConsume .next ().consumeAggs ()) {
408+ reducer = createReducer (resultSetSize , reduceContext , delayable .expand ());
409+ }
410+ return consumeAggResults (partialResults , toConsume , reducer );
422411 } finally {
423412 toConsume .forEachRemaining (QuerySearchResult ::releaseAggs );
424413 }
425414 }
426415
416+ private static AggregatorsReducer createReducer (int resultSetSize , AggregationReduceContext reduceContext , InternalAggregations first ) {
417+ boolean success = false ;
418+ var reducer = new AggregatorsReducer (first , reduceContext , resultSetSize );
419+ try {
420+ reducer .accept (first );
421+ success = true ;
422+ return reducer ;
423+ } finally {
424+ if (success == false ) {
425+ reducer .close ();
426+ }
427+ }
428+ }
429+
430+ private static InternalAggregations consumeAggResults (
431+ Iterator <InternalAggregations > partialResults ,
432+ Iterator <QuerySearchResult > toConsume ,
433+ AggregatorsReducer reducer
434+ ) {
435+ try (reducer ) {
436+ partialResults .forEachRemaining (reducer ::accept );
437+ while (toConsume .hasNext ()) {
438+ try (var delayable = toConsume .next ().consumeAggs ()) {
439+ reducer .accept (delayable .expand ());
440+ }
441+ }
442+ return reducer .get ();
443+ }
444+ }
445+
427446 public int getNumReducePhases () {
428447 return numReducePhases ;
429448 }
0 commit comments