Skip to content

Commit c7ee96c

Browse files
Faster and shorter SearchPhaseController.reduceQueryPhase (#119855) (#122092)
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 a18856e commit c7ee96c

File tree

1 file changed

+27
-36
lines changed

1 file changed

+27
-36
lines changed

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

Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -420,9 +420,8 @@ static ReducedQueryPhase reducedQueryPhase(
420420
assert numReducePhases >= 0 : "num reduce phases must be >= 0 but was: " + numReducePhases;
421421
numReducePhases++; // increment for this phase
422422
if (queryResults.isEmpty()) { // early terminate we have nothing to reduce
423-
final TotalHits totalHits = topDocsStats.getTotalHits();
424423
return new ReducedQueryPhase(
425-
totalHits,
424+
topDocsStats.getTotalHits(),
426425
topDocsStats.fetchHits,
427426
topDocsStats.getMaxScore(),
428427
false,
@@ -439,8 +438,7 @@ static ReducedQueryPhase reducedQueryPhase(
439438
true
440439
);
441440
}
442-
int total = queryResults.size();
443-
final Collection<SearchPhaseResult> nonNullResults = new ArrayList<>();
441+
final List<QuerySearchResult> nonNullResults = new ArrayList<>();
444442
boolean hasSuggest = false;
445443
boolean hasProfileResults = false;
446444
for (SearchPhaseResult queryResult : queryResults) {
@@ -450,26 +448,24 @@ static ReducedQueryPhase reducedQueryPhase(
450448
}
451449
hasSuggest |= res.suggest() != null;
452450
hasProfileResults |= res.hasProfileResults();
453-
nonNullResults.add(queryResult);
451+
nonNullResults.add(res);
454452
}
455-
queryResults = nonNullResults;
456-
validateMergeSortValueFormats(queryResults);
457-
if (queryResults.isEmpty()) {
458-
var ex = new IllegalStateException("must have at least one non-empty search result, got 0 out of " + total);
453+
validateMergeSortValueFormats(nonNullResults);
454+
if (nonNullResults.isEmpty()) {
455+
var ex = new IllegalStateException("must have at least one non-empty search result, got 0 out of " + queryResults.size());
459456
assert false : ex;
460457
throw ex;
461458
}
462459

463460
// count the total (we use the query result provider here, since we might not get any hits (we scrolled past them))
464461
final Map<String, List<Suggestion<?>>> groupedSuggestions = hasSuggest ? new HashMap<>() : Collections.emptyMap();
465462
final Map<String, SearchProfileQueryPhaseResult> profileShardResults = hasProfileResults
466-
? Maps.newMapWithExpectedSize(queryResults.size())
463+
? Maps.newMapWithExpectedSize(nonNullResults.size())
467464
: Collections.emptyMap();
468465
int from = 0;
469466
int size = 0;
470467
DocValueFormat[] sortValueFormats = null;
471-
for (SearchPhaseResult entry : queryResults) {
472-
QuerySearchResult result = entry.queryResult();
468+
for (QuerySearchResult result : nonNullResults) {
473469
from = result.from();
474470
// sorted queries can set the size to 0 if they have enough competitive hits.
475471
size = Math.max(result.size(), size);
@@ -480,49 +476,44 @@ static ReducedQueryPhase reducedQueryPhase(
480476
if (hasSuggest) {
481477
assert result.suggest() != null;
482478
for (Suggestion<? extends Suggestion.Entry<? extends Suggestion.Entry.Option>> suggestion : result.suggest()) {
483-
List<Suggestion<?>> suggestionList = groupedSuggestions.computeIfAbsent(suggestion.getName(), s -> new ArrayList<>());
484-
suggestionList.add(suggestion);
479+
groupedSuggestions.computeIfAbsent(suggestion.getName(), s -> new ArrayList<>()).add(suggestion);
485480
if (suggestion instanceof CompletionSuggestion completionSuggestion) {
486481
completionSuggestion.setShardIndex(result.getShardIndex());
487482
}
488483
}
489484
}
490485
assert bufferedTopDocs.isEmpty() || result.hasConsumedTopDocs() : "firstResult has no aggs but we got non null buffered aggs?";
491486
if (hasProfileResults) {
492-
String key = result.getSearchShardTarget().toString();
493-
profileShardResults.put(key, result.consumeProfileResult());
487+
profileShardResults.put(result.getSearchShardTarget().toString(), result.consumeProfileResult());
494488
}
495489
}
496-
final Suggest reducedSuggest;
497-
final List<CompletionSuggestion> reducedCompletionSuggestions;
498-
if (groupedSuggestions.isEmpty()) {
499-
reducedSuggest = null;
500-
reducedCompletionSuggestions = Collections.emptyList();
501-
} else {
502-
reducedSuggest = new Suggest(Suggest.reduce(groupedSuggestions));
503-
reducedCompletionSuggestions = reducedSuggest.filter(CompletionSuggestion.class);
504-
}
505-
final SearchProfileResultsBuilder profileBuilder = profileShardResults.isEmpty()
506-
? null
507-
: new SearchProfileResultsBuilder(profileShardResults);
490+
final Suggest reducedSuggest = groupedSuggestions.isEmpty() ? null : new Suggest(Suggest.reduce(groupedSuggestions));
508491
final SortedTopDocs sortedTopDocs;
509492
if (queryPhaseRankCoordinatorContext == null) {
510-
sortedTopDocs = sortDocs(isScrollRequest, bufferedTopDocs, from, size, reducedCompletionSuggestions);
493+
sortedTopDocs = sortDocs(
494+
isScrollRequest,
495+
bufferedTopDocs,
496+
from,
497+
size,
498+
reducedSuggest == null ? Collections.emptyList() : reducedSuggest.filter(CompletionSuggestion.class)
499+
);
511500
} else {
512-
ScoreDoc[] rankedDocs = queryPhaseRankCoordinatorContext.rankQueryPhaseResults(
513-
queryResults.stream().map(SearchPhaseResult::queryResult).toList(),
514-
topDocsStats
501+
sortedTopDocs = new SortedTopDocs(
502+
queryPhaseRankCoordinatorContext.rankQueryPhaseResults(nonNullResults, topDocsStats),
503+
false,
504+
null,
505+
null,
506+
null,
507+
0
515508
);
516-
sortedTopDocs = new SortedTopDocs(rankedDocs, false, null, null, null, 0);
517509
size = sortedTopDocs.scoreDocs.length;
518510
// we need to reset from here as pagination and result trimming has already taken place
519511
// within the `QueryPhaseRankCoordinatorContext#rankQueryPhaseResults` and we don't want
520512
// to apply it again in the `getHits` method.
521513
from = 0;
522514
}
523-
final TotalHits totalHits = topDocsStats.getTotalHits();
524515
return new ReducedQueryPhase(
525-
totalHits,
516+
topDocsStats.getTotalHits(),
526517
topDocsStats.fetchHits,
527518
topDocsStats.getMaxScore(),
528519
topDocsStats.timedOut,
@@ -534,7 +525,7 @@ static ReducedQueryPhase reducedQueryPhase(
534525
bufferedAggs,
535526
performFinalReduce ? aggReduceContextBuilder.forFinalReduction() : aggReduceContextBuilder.forPartialReduction()
536527
),
537-
profileBuilder,
528+
profileShardResults.isEmpty() ? null : new SearchProfileResultsBuilder(profileShardResults),
538529
sortedTopDocs,
539530
sortValueFormats,
540531
queryPhaseRankCoordinatorContext,

0 commit comments

Comments
 (0)