@@ -307,16 +307,12 @@ public List<Aggregator> createTopLevelNonGlobalAggregators(SearchContext searchC
307307
308308 private List <Aggregator > createTopLevelAggregators (SearchContext searchContext , Predicate <AggregatorFactory > factoryFilter )
309309 throws IOException {
310- // Estimate streaming cost from factories BEFORE creating any aggregators.
311- // This allows the correct aggregator type to be created on the first try,
312- // avoiding double-creation when streaming is not beneficial.
313310 if (searchContext .isStreamSearch () && searchContext .getFlushMode () == null ) {
314- StreamingCostMetrics metrics = estimateStreamingCostFromFactories (factories , searchContext );
315311 FlushMode decision ;
316- if (metrics == null ) {
317- // No factories provided streaming metrics - default to PER_SHARD
312+ if (factories .length == 0 ) {
318313 decision = FlushMode .PER_SHARD ;
319314 } else {
315+ StreamingCostMetrics metrics = estimateStreamingCostFromFactories (factories , searchContext );
320316 long maxBucket = searchContext .getStreamingMaxEstimatedBucketCount ();
321317 double minRatio = searchContext .getStreamingMinCardinalityRatio ();
322318 long minBucket = searchContext .getStreamingMinEstimatedBucketCount ();
@@ -348,25 +344,19 @@ private List<Aggregator> createTopLevelAggregators(SearchContext searchContext,
348344 /**
349345 * Recursively estimates streaming cost from the factory tree.
350346 *
351- * <p>Traverses the aggregator factory tree, collecting streaming cost metrics from factories
352- * that implement {@link StreamingCostEstimable}. Combines metrics from sibling factories
353- * and nested sub-aggregations to produce a combined estimate.
354- *
355- * @param factories Array of aggregator factories to estimate
347+ * @param factories Array of aggregator factories to estimate (must be non-empty)
356348 * @param searchContext Search context providing access to index metadata
357- * @return Combined streaming cost metrics, null if no factories provide metrics,
358- * or non-streamable if any factory explicitly returns non-streamable
349+ * @return Combined streaming cost metrics, or non-streamable if any factory cannot be streamed
359350 */
360351 private static StreamingCostMetrics estimateStreamingCostFromFactories (AggregatorFactory [] factories , SearchContext searchContext ) {
352+ assert factories .length > 0 : "factories array must be non-empty" ;
361353 StreamingCostMetrics combined = null ;
362354 for (AggregatorFactory factory : factories ) {
363355 StreamingCostMetrics metrics = estimateFromFactory (factory , searchContext );
364- if (metrics != null && !metrics .streamable ()) {
356+ if (!metrics .streamable ()) {
365357 return StreamingCostMetrics .nonStreamable ();
366358 }
367- if (metrics != null ) {
368- combined = (combined == null ) ? metrics : combined .combineWithSibling (metrics );
369- }
359+ combined = (combined == null ) ? metrics : combined .combineWithSibling (metrics );
370360 }
371361 return combined ;
372362 }
@@ -380,30 +370,26 @@ private static StreamingCostMetrics estimateStreamingCostFromFactories(Aggregato
380370 *
381371 * @param factory The aggregator factory to estimate
382372 * @param searchContext Search context providing access to index metadata
383- * @return Streaming cost metrics for this factory and its sub-aggregations
373+ * @return Streaming cost metrics for this factory and its sub-aggregations (never null)
384374 */
385375 private static StreamingCostMetrics estimateFromFactory (AggregatorFactory factory , SearchContext searchContext ) {
386- StreamingCostMetrics factoryMetrics ;
387- if (factory instanceof StreamingCostEstimable estimable ) {
388- factoryMetrics = estimable .estimateStreamingCost (searchContext );
389- if (!factoryMetrics .streamable ()) {
390- return StreamingCostMetrics .nonStreamable ();
391- }
392- } else {
393- // Factory doesn't implement StreamingCostEstimable - not streaming compatible
376+ if (!(factory instanceof StreamingCostEstimable estimable )) {
377+ return StreamingCostMetrics .nonStreamable ();
378+ }
379+
380+ StreamingCostMetrics factoryMetrics = estimable .estimateStreamingCost (searchContext );
381+ if (!factoryMetrics .streamable ()) {
394382 return StreamingCostMetrics .nonStreamable ();
395383 }
396384
397385 // Recursively estimate sub-factories
398386 AggregatorFactory [] subFactories = factory .getSubFactories ().getFactories ();
399387 if (subFactories .length > 0 ) {
400388 StreamingCostMetrics subMetrics = estimateStreamingCostFromFactories (subFactories , searchContext );
401- if (subMetrics != null && !subMetrics .streamable ()) {
389+ if (!subMetrics .streamable ()) {
402390 return StreamingCostMetrics .nonStreamable ();
403391 }
404- if (subMetrics != null ) {
405- factoryMetrics = factoryMetrics .combineWithSubAggregation (subMetrics );
406- }
392+ factoryMetrics = factoryMetrics .combineWithSubAggregation (subMetrics );
407393 }
408394
409395 return factoryMetrics ;
0 commit comments