Skip to content

Commit 0ca32f5

Browse files
Faster and shorter SearchPhaseController.reduceQueryPhase (#119855)
We can avoid one list copy and some indirection by collecting to a list of non-null query responses instead of non-null generic responses right away (this also avoids the unfortunate reassignment of a method parameter). Also, this method is fairly long, this at least removes all redundant local variables and as a result a bit of computation in some cases.
1 parent 16c7ec6 commit 0ca32f5

File tree

1 file changed

+33
-43
lines changed

1 file changed

+33
-43
lines changed

server/src/main/java/org/elasticsearch/action/search/SearchPhaseController.java

Lines changed: 33 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -551,9 +551,8 @@ static ReducedQueryPhase reducedQueryPhase(
551551
assert numReducePhases >= 0 : "num reduce phases must be >= 0 but was: " + numReducePhases;
552552
numReducePhases++; // increment for this phase
553553
if (queryResults.isEmpty()) { // early terminate we have nothing to reduce
554-
final TotalHits totalHits = topDocsStats.getTotalHits();
555554
return new ReducedQueryPhase(
556-
totalHits,
555+
topDocsStats.getTotalHits(),
557556
topDocsStats.fetchHits,
558557
topDocsStats.getMaxScore(),
559558
false,
@@ -570,8 +569,7 @@ static ReducedQueryPhase reducedQueryPhase(
570569
true
571570
);
572571
}
573-
int total = queryResults.size();
574-
final Collection<SearchPhaseResult> nonNullResults = new ArrayList<>();
572+
final List<QuerySearchResult> nonNullResults = new ArrayList<>();
575573
boolean hasSuggest = false;
576574
boolean hasProfileResults = false;
577575
for (SearchPhaseResult queryResult : queryResults) {
@@ -581,26 +579,24 @@ static ReducedQueryPhase reducedQueryPhase(
581579
}
582580
hasSuggest |= res.suggest() != null;
583581
hasProfileResults |= res.hasProfileResults();
584-
nonNullResults.add(queryResult);
582+
nonNullResults.add(res);
585583
}
586-
queryResults = nonNullResults;
587-
validateMergeSortValueFormats(queryResults);
588-
if (queryResults.isEmpty()) {
589-
var ex = new IllegalStateException("must have at least one non-empty search result, got 0 out of " + total);
584+
validateMergeSortValueFormats(nonNullResults);
585+
if (nonNullResults.isEmpty()) {
586+
var ex = new IllegalStateException("must have at least one non-empty search result, got 0 out of " + queryResults.size());
590587
assert false : ex;
591588
throw ex;
592589
}
593590

594591
// count the total (we use the query result provider here, since we might not get any hits (we scrolled past them))
595592
final Map<String, List<Suggestion<?>>> groupedSuggestions = hasSuggest ? new HashMap<>() : Collections.emptyMap();
596593
final Map<String, SearchProfileQueryPhaseResult> profileShardResults = hasProfileResults
597-
? Maps.newMapWithExpectedSize(queryResults.size())
594+
? Maps.newMapWithExpectedSize(nonNullResults.size())
598595
: Collections.emptyMap();
599596
int from = 0;
600597
int size = 0;
601598
DocValueFormat[] sortValueFormats = null;
602-
for (SearchPhaseResult entry : queryResults) {
603-
QuerySearchResult result = entry.queryResult();
599+
for (QuerySearchResult result : nonNullResults) {
604600
from = result.from();
605601
// sorted queries can set the size to 0 if they have enough competitive hits.
606602
size = Math.max(result.size(), size);
@@ -611,62 +607,56 @@ static ReducedQueryPhase reducedQueryPhase(
611607
if (hasSuggest) {
612608
assert result.suggest() != null;
613609
for (Suggestion<? extends Suggestion.Entry<? extends Suggestion.Entry.Option>> suggestion : result.suggest()) {
614-
List<Suggestion<?>> suggestionList = groupedSuggestions.computeIfAbsent(suggestion.getName(), s -> new ArrayList<>());
615-
suggestionList.add(suggestion);
610+
groupedSuggestions.computeIfAbsent(suggestion.getName(), s -> new ArrayList<>()).add(suggestion);
616611
if (suggestion instanceof CompletionSuggestion completionSuggestion) {
617612
completionSuggestion.setShardIndex(result.getShardIndex());
618613
}
619614
}
620615
}
621616
assert bufferedTopDocs.isEmpty() || result.hasConsumedTopDocs() : "firstResult has no aggs but we got non null buffered aggs?";
622617
if (hasProfileResults) {
623-
String key = result.getSearchShardTarget().toString();
624-
profileShardResults.put(key, result.consumeProfileResult());
618+
profileShardResults.put(result.getSearchShardTarget().toString(), result.consumeProfileResult());
625619
}
626620
}
627-
final Suggest reducedSuggest;
628-
final List<CompletionSuggestion> reducedCompletionSuggestions;
629-
if (groupedSuggestions.isEmpty()) {
630-
reducedSuggest = null;
631-
reducedCompletionSuggestions = Collections.emptyList();
632-
} else {
633-
reducedSuggest = new Suggest(Suggest.reduce(groupedSuggestions));
634-
reducedCompletionSuggestions = reducedSuggest.filter(CompletionSuggestion.class);
635-
}
636-
final InternalAggregations aggregations = bufferedAggs == null
637-
? null
638-
: InternalAggregations.topLevelReduceDelayable(
639-
bufferedAggs,
640-
performFinalReduce ? aggReduceContextBuilder.forFinalReduction() : aggReduceContextBuilder.forPartialReduction()
641-
);
642-
final SearchProfileResultsBuilder profileBuilder = profileShardResults.isEmpty()
643-
? null
644-
: new SearchProfileResultsBuilder(profileShardResults);
621+
final Suggest reducedSuggest = groupedSuggestions.isEmpty() ? null : new Suggest(Suggest.reduce(groupedSuggestions));
645622
final SortedTopDocs sortedTopDocs;
646623
if (queryPhaseRankCoordinatorContext == null) {
647-
sortedTopDocs = sortDocs(isScrollRequest, bufferedTopDocs, from, size, reducedCompletionSuggestions);
624+
sortedTopDocs = sortDocs(
625+
isScrollRequest,
626+
bufferedTopDocs,
627+
from,
628+
size,
629+
reducedSuggest == null ? Collections.emptyList() : reducedSuggest.filter(CompletionSuggestion.class)
630+
);
648631
} else {
649-
ScoreDoc[] rankedDocs = queryPhaseRankCoordinatorContext.rankQueryPhaseResults(
650-
queryResults.stream().map(SearchPhaseResult::queryResult).toList(),
651-
topDocsStats
632+
sortedTopDocs = new SortedTopDocs(
633+
queryPhaseRankCoordinatorContext.rankQueryPhaseResults(nonNullResults, topDocsStats),
634+
false,
635+
null,
636+
null,
637+
null,
638+
0
652639
);
653-
sortedTopDocs = new SortedTopDocs(rankedDocs, false, null, null, null, 0);
654640
size = sortedTopDocs.scoreDocs.length;
655641
// we need to reset from here as pagination and result trimming has already taken place
656642
// within the `QueryPhaseRankCoordinatorContext#rankQueryPhaseResults` and we don't want
657643
// to apply it again in the `getHits` method.
658644
from = 0;
659645
}
660-
final TotalHits totalHits = topDocsStats.getTotalHits();
661646
return new ReducedQueryPhase(
662-
totalHits,
647+
topDocsStats.getTotalHits(),
663648
topDocsStats.fetchHits,
664649
topDocsStats.getMaxScore(),
665650
topDocsStats.timedOut,
666651
topDocsStats.terminatedEarly,
667652
reducedSuggest,
668-
aggregations,
669-
profileBuilder,
653+
bufferedAggs == null
654+
? null
655+
: InternalAggregations.topLevelReduceDelayable(
656+
bufferedAggs,
657+
performFinalReduce ? aggReduceContextBuilder.forFinalReduction() : aggReduceContextBuilder.forPartialReduction()
658+
),
659+
profileShardResults.isEmpty() ? null : new SearchProfileResultsBuilder(profileShardResults),
670660
sortedTopDocs,
671661
sortValueFormats,
672662
queryPhaseRankCoordinatorContext,

0 commit comments

Comments
 (0)