Skip to content

Commit bf02765

Browse files
authored
Merge branch 'main' into fix/index_time_scripts_doc_value_fetcher
2 parents 6f9fa17 + 610722d commit bf02765

File tree

19 files changed

+1025
-104
lines changed

19 files changed

+1025
-104
lines changed

docs/changelog/122199.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 122199
2+
summary: Fix issues that prevents using search only snapshots for indices that use index sorting. This is includes Logsdb and time series indices.
3+
area: Logs
4+
type: bug
5+
issues: []

docs/changelog/122357.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 122357
2+
summary: Handle search timeout in `SuggestPhase`
3+
area: Search
4+
type: bug
5+
issues:
6+
- 122186

docs/changelog/122496.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 122496
2+
summary: Deduplicate `IngestStats` and `IngestStats.Stats` identity records when deserializing
3+
area: Ingest Node
4+
type: bug
5+
issues: []

muted-tests.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -332,9 +332,6 @@ tests:
332332
- class: org.elasticsearch.backwards.MixedClusterClientYamlTestSuiteIT
333333
method: test {p0=search.vectors/42_knn_search_int4_flat/Vector similarity with filter only}
334334
issue: https://github.com/elastic/elasticsearch/issues/121412
335-
- class: org.elasticsearch.search.CrossClusterSearchUnavailableClusterIT
336-
method: testSearchSkipUnavailable
337-
issue: https://github.com/elastic/elasticsearch/issues/121497
338335
- class: org.elasticsearch.smoketest.DocsClientYamlTestSuiteIT
339336
method: test {yaml=reference/cat/health/cat-health-no-timestamp-example}
340337
issue: https://github.com/elastic/elasticsearch/issues/121867
@@ -343,9 +340,6 @@ tests:
343340
issue: https://github.com/elastic/elasticsearch/issues/121625
344341
- class: org.elasticsearch.xpack.searchablesnapshots.hdfs.SecureHdfsSearchableSnapshotsIT
345342
issue: https://github.com/elastic/elasticsearch/issues/121967
346-
- class: org.elasticsearch.action.search.SearchQueryThenFetchAsyncActionTests
347-
method: testBottomFieldSort
348-
issue: https://github.com/elastic/elasticsearch/issues/121503
349343
- class: org.elasticsearch.xpack.application.CohereServiceUpgradeIT
350344
issue: https://github.com/elastic/elasticsearch/issues/121537
351345
- class: org.elasticsearch.xpack.restart.FullClusterRestartIT

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

Lines changed: 239 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.apache.lucene.search.ConstantScoreScorer;
1515
import org.apache.lucene.search.ConstantScoreWeight;
1616
import org.apache.lucene.search.DocIdSetIterator;
17+
import org.apache.lucene.search.Explanation;
1718
import org.apache.lucene.search.IndexSearcher;
1819
import org.apache.lucene.search.LeafCollector;
1920
import org.apache.lucene.search.Query;
@@ -22,8 +23,10 @@
2223
import org.apache.lucene.search.ScoreMode;
2324
import org.apache.lucene.search.Scorer;
2425
import org.apache.lucene.search.ScorerSupplier;
26+
import org.apache.lucene.search.TopDocs;
2527
import org.apache.lucene.search.Weight;
2628
import org.apache.lucene.util.Bits;
29+
import org.apache.lucene.util.CharsRefBuilder;
2730
import org.elasticsearch.ElasticsearchException;
2831
import org.elasticsearch.TransportVersion;
2932
import org.elasticsearch.action.search.SearchRequestBuilder;
@@ -33,12 +36,23 @@
3336
import org.elasticsearch.core.TimeValue;
3437
import org.elasticsearch.index.query.AbstractQueryBuilder;
3538
import org.elasticsearch.index.query.QueryBuilder;
39+
import org.elasticsearch.index.query.QueryRewriteContext;
3640
import org.elasticsearch.index.query.SearchExecutionContext;
41+
import org.elasticsearch.index.query.TermQueryBuilder;
3742
import org.elasticsearch.plugins.Plugin;
3843
import org.elasticsearch.plugins.SearchPlugin;
3944
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
4045
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
4146
import org.elasticsearch.search.internal.ContextIndexSearcher;
47+
import org.elasticsearch.search.rescore.RescoreContext;
48+
import org.elasticsearch.search.rescore.Rescorer;
49+
import org.elasticsearch.search.rescore.RescorerBuilder;
50+
import org.elasticsearch.search.suggest.SortBy;
51+
import org.elasticsearch.search.suggest.SuggestBuilder;
52+
import org.elasticsearch.search.suggest.Suggester;
53+
import org.elasticsearch.search.suggest.SuggestionSearchContext;
54+
import org.elasticsearch.search.suggest.term.TermSuggestion;
55+
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
4256
import org.elasticsearch.test.ESIntegTestCase;
4357
import org.elasticsearch.test.hamcrest.ElasticsearchAssertions;
4458
import org.elasticsearch.xcontent.XContentBuilder;
@@ -58,7 +72,7 @@ public class SearchTimeoutIT extends ESIntegTestCase {
5872

5973
@Override
6074
protected Collection<Class<? extends Plugin>> nodePlugins() {
61-
return Collections.singleton(BulkScorerTimeoutQueryPlugin.class);
75+
return Collections.singleton(SearchTimeoutPlugin.class);
6276
}
6377

6478
@Override
@@ -72,6 +86,9 @@ protected void setupSuiteScopeCluster() throws Exception {
7286
indexRandom(true, "test", randomIntBetween(20, 50));
7387
}
7488

89+
/**
90+
* Test the scenario where the query times out before starting to collect documents, verify that partial hits are not returned
91+
*/
7592
public void testTopHitsTimeoutBeforeCollecting() {
7693
// setting the timeout is necessary only because we check that if a TimeExceededException is thrown, a timeout was set
7794
SearchRequestBuilder searchRequestBuilder = prepareSearch("test").setTimeout(new TimeValue(10, TimeUnit.SECONDS))
@@ -88,6 +105,9 @@ public void testTopHitsTimeoutBeforeCollecting() {
88105
});
89106
}
90107

108+
/**
109+
* Test the scenario where the query times out while collecting documents, verify that partial hits results are returned
110+
*/
91111
public void testTopHitsTimeoutWhileCollecting() {
92112
// setting the timeout is necessary only because we check that if a TimeExceededException is thrown, a timeout was set
93113
SearchRequestBuilder searchRequestBuilder = prepareSearch("test").setTimeout(new TimeValue(10, TimeUnit.SECONDS))
@@ -103,6 +123,9 @@ public void testTopHitsTimeoutWhileCollecting() {
103123
});
104124
}
105125

126+
/**
127+
* Test the scenario where the query times out before starting to collect documents, verify that partial aggs results are not returned
128+
*/
106129
public void testAggsTimeoutBeforeCollecting() {
107130
SearchRequestBuilder searchRequestBuilder = prepareSearch("test").setSize(0)
108131
// setting the timeout is necessary only because we check that if a TimeExceededException is thrown, a timeout was set
@@ -123,6 +146,9 @@ public void testAggsTimeoutBeforeCollecting() {
123146
});
124147
}
125148

149+
/**
150+
* Test the scenario where the query times out while collecting documents, verify that partial aggs results are returned
151+
*/
126152
public void testAggsTimeoutWhileCollecting() {
127153
SearchRequestBuilder searchRequestBuilder = prepareSearch("test").setSize(0)
128154
// setting the timeout is necessary only because we check that if a TimeExceededException is thrown, a timeout was set
@@ -145,6 +171,56 @@ public void testAggsTimeoutWhileCollecting() {
145171
});
146172
}
147173

174+
/**
175+
* Test the scenario where the suggest phase (part of the query phase) times out, yet there are results
176+
* available coming from executing the query and aggs on each shard.
177+
*/
178+
public void testSuggestTimeoutWithPartialResults() {
179+
SuggestBuilder suggestBuilder = new SuggestBuilder();
180+
suggestBuilder.setGlobalText("text");
181+
TimeoutSuggestionBuilder timeoutSuggestionBuilder = new TimeoutSuggestionBuilder();
182+
suggestBuilder.addSuggestion("suggest", timeoutSuggestionBuilder);
183+
SearchRequestBuilder searchRequestBuilder = prepareSearch("test").suggest(suggestBuilder)
184+
.addAggregation(new TermsAggregationBuilder("terms").field("field.keyword"));
185+
ElasticsearchAssertions.assertResponse(searchRequestBuilder, searchResponse -> {
186+
assertThat(searchResponse.isTimedOut(), equalTo(true));
187+
assertEquals(0, searchResponse.getShardFailures().length);
188+
assertEquals(0, searchResponse.getFailedShards());
189+
assertThat(searchResponse.getSuccessfulShards(), greaterThan(0));
190+
assertEquals(searchResponse.getSuccessfulShards(), searchResponse.getTotalShards());
191+
assertThat(searchResponse.getHits().getTotalHits().value(), greaterThan(0L));
192+
assertThat(searchResponse.getHits().getHits().length, greaterThan(0));
193+
StringTerms terms = searchResponse.getAggregations().get("terms");
194+
assertEquals(1, terms.getBuckets().size());
195+
StringTerms.Bucket bucket = terms.getBuckets().get(0);
196+
assertEquals("value", bucket.getKeyAsString());
197+
assertThat(bucket.getDocCount(), greaterThan(0L));
198+
});
199+
}
200+
201+
/**
202+
* Test the scenario where the rescore phase (part of the query phase) times out, yet there are results
203+
* available coming from executing the query and aggs on each shard.
204+
*/
205+
public void testRescoreTimeoutWithPartialResults() {
206+
SearchRequestBuilder searchRequestBuilder = prepareSearch("test").setRescorer(new TimeoutRescorerBuilder())
207+
.addAggregation(new TermsAggregationBuilder("terms").field("field.keyword"));
208+
ElasticsearchAssertions.assertResponse(searchRequestBuilder, searchResponse -> {
209+
assertThat(searchResponse.isTimedOut(), equalTo(true));
210+
assertEquals(0, searchResponse.getShardFailures().length);
211+
assertEquals(0, searchResponse.getFailedShards());
212+
assertThat(searchResponse.getSuccessfulShards(), greaterThan(0));
213+
assertEquals(searchResponse.getSuccessfulShards(), searchResponse.getTotalShards());
214+
assertThat(searchResponse.getHits().getTotalHits().value(), greaterThan(0L));
215+
assertThat(searchResponse.getHits().getHits().length, greaterThan(0));
216+
StringTerms terms = searchResponse.getAggregations().get("terms");
217+
assertEquals(1, terms.getBuckets().size());
218+
StringTerms.Bucket bucket = terms.getBuckets().get(0);
219+
assertEquals("value", bucket.getKeyAsString());
220+
assertThat(bucket.getDocCount(), greaterThan(0L));
221+
});
222+
}
223+
148224
public void testPartialResultsIntolerantTimeoutBeforeCollecting() {
149225
ElasticsearchException ex = expectThrows(
150226
ElasticsearchException.class,
@@ -171,13 +247,67 @@ public void testPartialResultsIntolerantTimeoutWhileCollecting() {
171247
assertEquals(429, ex.status().getStatus());
172248
}
173249

174-
public static final class BulkScorerTimeoutQueryPlugin extends Plugin implements SearchPlugin {
250+
public void testPartialResultsIntolerantTimeoutWhileSuggestingOnly() {
251+
SuggestBuilder suggestBuilder = new SuggestBuilder();
252+
suggestBuilder.setGlobalText("text");
253+
TimeoutSuggestionBuilder timeoutSuggestionBuilder = new TimeoutSuggestionBuilder();
254+
suggestBuilder.addSuggestion("suggest", timeoutSuggestionBuilder);
255+
ElasticsearchException ex = expectThrows(
256+
ElasticsearchException.class,
257+
prepareSearch("test").suggest(suggestBuilder).setAllowPartialSearchResults(false) // this line causes timeouts to report
258+
// failures
259+
);
260+
assertTrue(ex.toString().contains("Time exceeded"));
261+
assertEquals(429, ex.status().getStatus());
262+
}
263+
264+
public void testPartialResultsIntolerantTimeoutWhileSuggesting() {
265+
SuggestBuilder suggestBuilder = new SuggestBuilder();
266+
suggestBuilder.setGlobalText("text");
267+
TimeoutSuggestionBuilder timeoutSuggestionBuilder = new TimeoutSuggestionBuilder();
268+
suggestBuilder.addSuggestion("suggest", timeoutSuggestionBuilder);
269+
ElasticsearchException ex = expectThrows(
270+
ElasticsearchException.class,
271+
prepareSearch("test").setQuery(new TermQueryBuilder("field", "value"))
272+
.suggest(suggestBuilder)
273+
.setAllowPartialSearchResults(false) // this line causes timeouts to report failures
274+
);
275+
assertTrue(ex.toString().contains("Time exceeded"));
276+
assertEquals(429, ex.status().getStatus());
277+
}
278+
279+
public void testPartialResultsIntolerantTimeoutWhileRescoring() {
280+
ElasticsearchException ex = expectThrows(
281+
ElasticsearchException.class,
282+
prepareSearch("test").setQuery(new TermQueryBuilder("field", "value"))
283+
.setRescorer(new TimeoutRescorerBuilder())
284+
.setAllowPartialSearchResults(false) // this line causes timeouts to report failures
285+
);
286+
assertTrue(ex.toString().contains("Time exceeded"));
287+
assertEquals(429, ex.status().getStatus());
288+
}
289+
290+
public static final class SearchTimeoutPlugin extends Plugin implements SearchPlugin {
175291
@Override
176292
public List<QuerySpec<?>> getQueries() {
177293
return Collections.singletonList(new QuerySpec<QueryBuilder>("timeout", BulkScorerTimeoutQuery::new, parser -> {
178294
throw new UnsupportedOperationException();
179295
}));
180296
}
297+
298+
@Override
299+
public List<SuggesterSpec<?>> getSuggesters() {
300+
return Collections.singletonList(new SuggesterSpec<>("timeout", TimeoutSuggestionBuilder::new, parser -> {
301+
throw new UnsupportedOperationException();
302+
}, TermSuggestion::new));
303+
}
304+
305+
@Override
306+
public List<RescorerSpec<?>> getRescorers() {
307+
return Collections.singletonList(new RescorerSpec<>("timeout", TimeoutRescorerBuilder::new, parser -> {
308+
throw new UnsupportedOperationException();
309+
}));
310+
}
181311
}
182312

183313
/**
@@ -315,4 +445,111 @@ public TransportVersion getMinimalSupportedVersion() {
315445
return null;
316446
}
317447
}
448+
449+
/**
450+
* Suggestion builder that triggers a timeout as part of its execution
451+
*/
452+
private static final class TimeoutSuggestionBuilder extends TermSuggestionBuilder {
453+
TimeoutSuggestionBuilder() {
454+
super("field");
455+
}
456+
457+
TimeoutSuggestionBuilder(StreamInput in) throws IOException {
458+
super(in);
459+
}
460+
461+
@Override
462+
public String getWriteableName() {
463+
return "timeout";
464+
}
465+
466+
@Override
467+
public SuggestionSearchContext.SuggestionContext build(SearchExecutionContext context) {
468+
return new TimeoutSuggestionContext(new TimeoutSuggester((ContextIndexSearcher) context.searcher()), context);
469+
}
470+
}
471+
472+
private static final class TimeoutSuggester extends Suggester<TimeoutSuggestionContext> {
473+
private final ContextIndexSearcher contextIndexSearcher;
474+
475+
TimeoutSuggester(ContextIndexSearcher contextIndexSearcher) {
476+
this.contextIndexSearcher = contextIndexSearcher;
477+
}
478+
479+
@Override
480+
protected TermSuggestion innerExecute(
481+
String name,
482+
TimeoutSuggestionContext suggestion,
483+
IndexSearcher searcher,
484+
CharsRefBuilder spare
485+
) {
486+
contextIndexSearcher.throwTimeExceededException();
487+
assert false;
488+
return new TermSuggestion(name, suggestion.getSize(), SortBy.SCORE);
489+
}
490+
491+
@Override
492+
protected TermSuggestion emptySuggestion(String name, TimeoutSuggestionContext suggestion, CharsRefBuilder spare) {
493+
return new TermSuggestion(name, suggestion.getSize(), SortBy.SCORE);
494+
}
495+
}
496+
497+
private static final class TimeoutSuggestionContext extends SuggestionSearchContext.SuggestionContext {
498+
TimeoutSuggestionContext(Suggester<?> suggester, SearchExecutionContext searchExecutionContext) {
499+
super(suggester, searchExecutionContext);
500+
}
501+
}
502+
503+
private static final class TimeoutRescorerBuilder extends RescorerBuilder<TimeoutRescorerBuilder> {
504+
TimeoutRescorerBuilder() {
505+
super();
506+
}
507+
508+
TimeoutRescorerBuilder(StreamInput in) throws IOException {
509+
super(in);
510+
}
511+
512+
@Override
513+
protected void doWriteTo(StreamOutput out) {}
514+
515+
@Override
516+
protected void doXContent(XContentBuilder builder, Params params) {}
517+
518+
@Override
519+
protected RescoreContext innerBuildContext(int windowSize, SearchExecutionContext context) throws IOException {
520+
return new RescoreContext(10, new Rescorer() {
521+
@Override
522+
public TopDocs rescore(TopDocs topDocs, IndexSearcher searcher, RescoreContext rescoreContext) {
523+
((ContextIndexSearcher) context.searcher()).throwTimeExceededException();
524+
assert false;
525+
return null;
526+
}
527+
528+
@Override
529+
public Explanation explain(
530+
int topLevelDocId,
531+
IndexSearcher searcher,
532+
RescoreContext rescoreContext,
533+
Explanation sourceExplanation
534+
) {
535+
throw new UnsupportedOperationException();
536+
}
537+
});
538+
}
539+
540+
@Override
541+
public String getWriteableName() {
542+
return "timeout";
543+
}
544+
545+
@Override
546+
public TransportVersion getMinimalSupportedVersion() {
547+
return null;
548+
}
549+
550+
@Override
551+
public RescorerBuilder<TimeoutRescorerBuilder> rewrite(QueryRewriteContext ctx) {
552+
return this;
553+
}
554+
}
318555
}

server/src/internalClusterTest/java/org/elasticsearch/search/functionscore/QueryRescorerIT.java

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import org.elasticsearch.common.lucene.search.function.ScoreFunction;
2525
import org.elasticsearch.common.settings.Settings;
2626
import org.elasticsearch.common.settings.Settings.Builder;
27-
import org.elasticsearch.core.TimeValue;
2827
import org.elasticsearch.index.query.Operator;
2928
import org.elasticsearch.index.query.QueryBuilder;
3029
import org.elasticsearch.index.query.QueryBuilders;
@@ -994,22 +993,6 @@ public void testRescoreAfterCollapseRandom() throws Exception {
994993
});
995994
}
996995

997-
public void testRescoreWithTimeout() throws Exception {
998-
// no dummy docs since merges can change scores while we run queries.
999-
int numDocs = indexRandomNumbers("whitespace", -1, false);
1000-
1001-
String intToEnglish = English.intToEnglish(between(0, numDocs - 1));
1002-
String query = intToEnglish.split(" ")[0];
1003-
assertResponse(
1004-
prepareSearch().setSearchType(SearchType.QUERY_THEN_FETCH)
1005-
.setQuery(QueryBuilders.matchQuery("field1", query).operator(Operator.OR))
1006-
.setSize(10)
1007-
.addRescorer(new QueryRescorerBuilder(functionScoreQuery(new TestTimedScoreFunctionBuilder())).windowSize(100))
1008-
.setTimeout(TimeValue.timeValueMillis(10)),
1009-
r -> assertTrue(r.isTimedOut())
1010-
);
1011-
}
1012-
1013996
@Override
1014997
protected Collection<Class<? extends Plugin>> nodePlugins() {
1015998
return List.of(TestTimedQueryPlugin.class);

0 commit comments

Comments
 (0)