Skip to content

Commit 9f541d9

Browse files
committed
Always create search context for scroll queries (#52078)
We need to either exclude null responses from the scroll search response or always create a search context for every target shards, although that scroll query can be written to match_no_docs. Otherwise, we won't find search_context for subsequent scroll requests. This commit implements the latter option as it's less error-prone. Relates #51708
1 parent 7c2fae1 commit 9f541d9

File tree

3 files changed

+42
-1
lines changed

3 files changed

+42
-1
lines changed

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -610,7 +610,8 @@ public final ShardSearchRequest buildShardSearchRequest(SearchShardIterator shar
610610
// if we already received a search result we can inform the shard that it
611611
// can return a null response if the request rewrites to match none rather
612612
// than creating an empty response in the search thread pool.
613-
shardRequest.canReturnNullResponseIfMatchNoDocs(hasShardResponse.get());
613+
// Note that, we have to disable this shortcut for scroll queries.
614+
shardRequest.canReturnNullResponseIfMatchNoDocs(hasShardResponse.get() && request.scroll() == null);
614615
return shardRequest;
615616
}
616617

server/src/main/java/org/elasticsearch/search/SearchService.java

+1
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ public void executeQueryPhase(ShardSearchRequest request, SearchShardTask task,
372372
if (rewritten.canReturnNullResponseIfMatchNoDocs()
373373
&& canRewriteToMatchNone(rewritten.source())
374374
&& rewritten.source().query() instanceof MatchNoneQueryBuilder) {
375+
assert request.scroll() == null : "must always create search context for scroll requests";
375376
onMatchNoDocs(context, listener);
376377
} else {
377378
// fork the execution in the search thread pool and wraps the searcher

server/src/test/java/org/elasticsearch/search/scroll/SearchScrollIT.java

+39
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.elasticsearch.common.xcontent.XContentHelper;
3636
import org.elasticsearch.index.IndexSettings;
3737
import org.elasticsearch.index.query.QueryBuilders;
38+
import org.elasticsearch.index.query.RangeQueryBuilder;
3839
import org.elasticsearch.rest.RestStatus;
3940
import org.elasticsearch.search.SearchHit;
4041
import org.elasticsearch.search.sort.FieldSortBuilder;
@@ -53,6 +54,7 @@
5354
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
5455
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
5556
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
57+
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
5658
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoSearchHits;
5759
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchHits;
5860
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse;
@@ -616,6 +618,43 @@ public void testInvalidScrollKeepAlive() throws IOException {
616618
assertThat(illegalArgumentException.getMessage(), containsString("Keep alive for scroll (3h) is too large"));
617619
}
618620

621+
/**
622+
* Ensures that we always create and retain search contexts on every target shards for a scroll request
623+
* regardless whether that query can be written to match_no_docs on some target shards or not.
624+
*/
625+
public void testScrollRewrittenToMatchNoDocs() {
626+
final int numShards = randomIntBetween(3, 5);
627+
assertAcked(
628+
client().admin().indices().prepareCreate("test")
629+
.setSettings(Settings.builder().put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, numShards))
630+
.addMapping("_doc", "created_date", "type=date,format=yyyy-MM-dd"));
631+
client().prepareIndex("test", "_doc").setId("1").setSource("created_date", "2020-01-01").get();
632+
client().prepareIndex("test", "_doc").setId("2").setSource("created_date", "2020-01-02").get();
633+
client().prepareIndex("test", "_doc").setId("3").setSource("created_date", "2020-01-03").get();
634+
client().admin().indices().prepareRefresh("test").get();
635+
SearchResponse resp = null;
636+
try {
637+
int totalHits = 0;
638+
resp = client().prepareSearch("test")
639+
.setQuery(new RangeQueryBuilder("created_date").gte("2020-01-02").lte("2020-01-03"))
640+
.setMaxConcurrentShardRequests(randomIntBetween(1, 3)) // sometimes fan out shard requests one by one
641+
.setSize(randomIntBetween(1, 2))
642+
.setScroll(TimeValue.timeValueMinutes(1))
643+
.get();
644+
assertNoFailures(resp);
645+
while (resp.getHits().getHits().length > 0) {
646+
totalHits += resp.getHits().getHits().length;
647+
resp = client().prepareSearchScroll(resp.getScrollId()).setScroll(TimeValue.timeValueMinutes(1)).get();
648+
assertNoFailures(resp);
649+
}
650+
assertThat(totalHits, equalTo(2));
651+
} finally {
652+
if (resp != null && resp.getScrollId() != null) {
653+
client().prepareClearScroll().addScrollId(resp.getScrollId()).get();
654+
}
655+
}
656+
}
657+
619658
private void assertToXContentResponse(ClearScrollResponse response, boolean succeed, int numFreed) throws IOException {
620659
XContentBuilder builder = XContentFactory.jsonBuilder();
621660
response.toXContent(builder, ToXContent.EMPTY_PARAMS);

0 commit comments

Comments
 (0)