Skip to content

Commit b2e6c15

Browse files
authored
Return an empty suggestion when suggest phase times out (#122575) (#122677)
We recently fixed timeout handling in the suggest phase. A test failure on SearchTimeoutIT surfaced an issue with the current approach. In case partial results are allowed, it may happen that some shards time out while executing the suggest phase and some don't. SearchPhaseController assumes that if one shard has suggest results, all of the other shards will have suggest results too. We could address that assertion and check is the search timed out, instead this commit changes timeout handling in the suggest phase to return an empty suggestion instead of null. This seems appropriate in terms of providing some results and makes the assertion about non null suggestions in SearchPhaseController happy. Relates to #122357 Closes #122548
1 parent e95cb35 commit b2e6c15

File tree

6 files changed

+38
-31
lines changed

6 files changed

+38
-31
lines changed

docs/changelog/122575.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 122575
2+
summary: Return an empty suggestion when suggest phase times out
3+
area: Suggesters
4+
type: bug
5+
issues:
6+
- 122548

server/src/internalClusterTest/java/org/elasticsearch/search/SearchTimeoutIT.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -483,8 +483,7 @@ protected TermSuggestion innerExecute(
483483
CharsRefBuilder spare
484484
) {
485485
contextIndexSearcher.throwTimeExceededException();
486-
assert false;
487-
return new TermSuggestion(name, suggestion.getSize(), SortBy.SCORE);
486+
throw new AssertionError("should have thrown TimeExceededException");
488487
}
489488

490489
@Override

server/src/main/java/org/elasticsearch/search/query/QueryPhase.java

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -126,15 +126,7 @@ static void executeRank(SearchContext searchContext) throws QueryPhaseExecutionE
126126

127127
static void executeQuery(SearchContext searchContext) throws QueryPhaseExecutionException {
128128
if (searchContext.hasOnlySuggest()) {
129-
try {
130-
SuggestPhase.execute(searchContext);
131-
} catch (ContextIndexSearcher.TimeExceededException timeExceededException) {
132-
SearchTimeoutException.handleTimeout(
133-
searchContext.request().allowPartialSearchResults(),
134-
searchContext.shardTarget(),
135-
searchContext.queryResult()
136-
);
137-
}
129+
SuggestPhase.execute(searchContext);
138130
searchContext.queryResult().topDocs(new TopDocsAndMaxScore(Lucene.EMPTY_TOP_DOCS, Float.NaN), new DocValueFormat[0]);
139131
return;
140132
}
@@ -150,18 +142,10 @@ static void executeQuery(SearchContext searchContext) throws QueryPhaseExecution
150142

151143
addCollectorsAndSearch(searchContext);
152144

153-
try {
154-
RescorePhase.execute(searchContext);
155-
SuggestPhase.execute(searchContext);
156-
if (searchContext.getProfilers() != null) {
157-
searchContext.queryResult().profileResults(searchContext.getProfilers().buildQueryPhaseResults());
158-
}
159-
} catch (ContextIndexSearcher.TimeExceededException timeExceededException) {
160-
SearchTimeoutException.handleTimeout(
161-
searchContext.request().allowPartialSearchResults(),
162-
searchContext.shardTarget(),
163-
searchContext.queryResult()
164-
);
145+
RescorePhase.execute(searchContext);
146+
SuggestPhase.execute(searchContext);
147+
if (searchContext.getProfilers() != null) {
148+
searchContext.queryResult().profileResults(searchContext.getProfilers().buildQueryPhaseResults());
165149
}
166150
}
167151

server/src/main/java/org/elasticsearch/search/rescore/RescorePhase.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
import org.elasticsearch.common.lucene.search.TopDocsAndMaxScore;
1919
import org.elasticsearch.common.util.Maps;
2020
import org.elasticsearch.lucene.grouping.TopFieldGroups;
21+
import org.elasticsearch.search.internal.ContextIndexSearcher;
2122
import org.elasticsearch.search.internal.SearchContext;
23+
import org.elasticsearch.search.query.SearchTimeoutException;
2224
import org.elasticsearch.search.sort.ShardDocSortField;
2325
import org.elasticsearch.search.sort.SortAndFormats;
2426

@@ -84,6 +86,13 @@ public static void execute(SearchContext context) {
8486
.topDocs(new TopDocsAndMaxScore(topDocs, topDocs.scoreDocs[0].score), context.queryResult().sortValueFormats());
8587
} catch (IOException e) {
8688
throw new ElasticsearchException("Rescore Phase Failed", e);
89+
} catch (ContextIndexSearcher.TimeExceededException timeExceededException) {
90+
SearchTimeoutException.handleTimeout(
91+
context.request().allowPartialSearchResults(),
92+
context.shardTarget(),
93+
context.queryResult()
94+
);
95+
// if the rescore phase times out and partial results are allowed, the returned top docs from this shard won't be rescored
8796
}
8897
}
8998

server/src/main/java/org/elasticsearch/search/suggest/SuggestPhase.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010

1111
import org.apache.lucene.util.CharsRefBuilder;
1212
import org.elasticsearch.ElasticsearchException;
13+
import org.elasticsearch.search.internal.ContextIndexSearcher;
1314
import org.elasticsearch.search.internal.SearchContext;
15+
import org.elasticsearch.search.query.SearchTimeoutException;
1416
import org.elasticsearch.search.suggest.Suggest.Suggestion;
1517
import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry;
1618
import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry.Option;
@@ -40,12 +42,17 @@ public static void execute(SearchContext context) {
4042
for (Map.Entry<String, SuggestionSearchContext.SuggestionContext> entry : suggest.suggestions().entrySet()) {
4143
SuggestionSearchContext.SuggestionContext suggestion = entry.getValue();
4244
Suggester<SuggestionContext> suggester = suggestion.getSuggester();
43-
Suggestion<? extends Entry<? extends Option>> result = suggester.execute(
44-
entry.getKey(),
45-
suggestion,
46-
context.searcher(),
47-
spare
48-
);
45+
Suggestion<? extends Entry<? extends Option>> result;
46+
try {
47+
result = suggester.execute(entry.getKey(), suggestion, context.searcher(), spare);
48+
} catch (ContextIndexSearcher.TimeExceededException timeExceededException) {
49+
SearchTimeoutException.handleTimeout(
50+
context.request().allowPartialSearchResults(),
51+
context.shardTarget(),
52+
context.queryResult()
53+
);
54+
result = suggester.emptySuggestion(entry.getKey(), suggestion, spare);
55+
}
4956
if (result != null) {
5057
assert entry.getKey().equals(result.name);
5158
suggestions.add(result);

server/src/test/java/org/elasticsearch/search/query/QueryPhaseTimeoutTests.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,8 @@ public void testSuggestOnlyWithTimeout() throws Exception {
280280
assertTrue(context.hasOnlySuggest());
281281
QueryPhase.execute(context);
282282
assertTrue(context.queryResult().searchTimedOut());
283-
assertNull(context.queryResult().suggest());
283+
assertEquals(1, context.queryResult().suggest().size());
284+
assertEquals(0, context.queryResult().suggest().getSuggestion("suggestion").getEntries().size());
284285
assertNotNull(context.queryResult().topDocs());
285286
assertEquals(0, context.queryResult().topDocs().topDocs.totalHits.value);
286287
}
@@ -294,7 +295,8 @@ public void testSuggestAndQueryWithSuggestTimeout() throws Exception {
294295
QueryPhase.execute(context);
295296
assertThat(context.queryResult().topDocs().topDocs.totalHits.value, Matchers.greaterThan(0L));
296297
assertTrue(context.queryResult().searchTimedOut());
297-
assertNull(context.queryResult().suggest());
298+
assertEquals(1, context.queryResult().suggest().size());
299+
assertEquals(0, context.queryResult().suggest().getSuggestion("suggestion").getEntries().size());
298300
}
299301
}
300302

0 commit comments

Comments
 (0)