Skip to content

Commit 9aed5f4

Browse files
authored
Refactor canMatch execution to expose time range filter to caller (#137314)
We recently introduced metrics attributes to track search latencies at the shard and coord level. With #137196 we are introducing such attributes to the can match phase latency metrics. The time range filter is not currently accessible when recording the metrics. This commit exposes it.
1 parent e1a9170 commit 9aed5f4

File tree

3 files changed

+59
-30
lines changed

3 files changed

+59
-30
lines changed

server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -893,7 +893,7 @@ public Relation isFieldWithinQuery(
893893
++fromInclusive;
894894
}
895895
// we set the time range filter from during rewrite, because this may be the only time we ever parse it,
896-
// in case the shard if filtered out and does not run the query phase or all its docs are within the bounds.
896+
// in case the shard gets filtered out and does not run the query phase or all its docs are within the bounds.
897897
context.setTimeRangeFilterFromMillis(fieldName, fromInclusive, resolution);
898898
}
899899

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

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -721,13 +721,7 @@ public void executeQueryPhase(ShardSearchRequest request, CancellableTask task,
721721
if (orig.canReturnNullResponseIfMatchNoDocs()) {
722722
assert orig.scroll() == null;
723723
ShardSearchRequest clone = new ShardSearchRequest(orig);
724-
CanMatchContext canMatchContext = new CanMatchContext(
725-
clone,
726-
indicesService::indexServiceSafe,
727-
this::findReaderContext,
728-
defaultKeepAlive,
729-
maxKeepAlive
730-
);
724+
CanMatchContext canMatchContext = createCanMatchContext(clone);
731725
CanMatchShardResponse canMatchResp = canMatch(canMatchContext, false);
732726
if (canMatchResp.canMatch() == false) {
733727
l.onResponse(QuerySearchResult.nullInstance());
@@ -1942,8 +1936,10 @@ public void canMatch(CanMatchNodeRequest request, ActionListener<CanMatchNodeRes
19421936
final IndexService indexService = indicesService.indexServiceSafe(shardSearchRequest.shardId().getIndex());
19431937
final IndexShard indexShard = indexService.getShard(shardSearchRequest.shardId().id());
19441938
try {
1945-
// TODO remove the exception handling as it's now in canMatch itself
1946-
responses.add(new CanMatchNodeResponse.ResponseOrFailure(canMatch(shardSearchRequest)));
1939+
// TODO remove the exception handling as it's now in canMatch itself - the failure no longer needs to be serialized
1940+
CanMatchContext canMatchContext = createCanMatchContext(shardSearchRequest);
1941+
CanMatchShardResponse canMatchShardResponse = canMatch(canMatchContext, true);
1942+
responses.add(new CanMatchNodeResponse.ResponseOrFailure(canMatchShardResponse));
19471943
indexShard.getSearchOperationListener().onCanMatchPhase(System.nanoTime() - shardCanMatchStartTimeInNanos);
19481944
} catch (Exception e) {
19491945
responses.add(new CanMatchNodeResponse.ResponseOrFailure(e));
@@ -1958,16 +1954,14 @@ public void canMatch(CanMatchNodeRequest request, ActionListener<CanMatchNodeRes
19581954
* won't match any documents on the current shard. Exceptions are handled within the method, and never re-thrown.
19591955
*/
19601956
public CanMatchShardResponse canMatch(ShardSearchRequest request) {
1961-
CanMatchContext canMatchContext = new CanMatchContext(
1962-
request,
1963-
indicesService::indexServiceSafe,
1964-
this::findReaderContext,
1965-
defaultKeepAlive,
1966-
maxKeepAlive
1967-
);
1957+
CanMatchContext canMatchContext = createCanMatchContext(request);
19681958
return canMatch(canMatchContext, true);
19691959
}
19701960

1961+
CanMatchContext createCanMatchContext(ShardSearchRequest request) {
1962+
return new CanMatchContext(request, indicesService::indexServiceSafe, this::findReaderContext, defaultKeepAlive, maxKeepAlive);
1963+
}
1964+
19711965
static class CanMatchContext {
19721966
private final ShardSearchRequest request;
19731967
private final Function<Index, IndexService> indexServiceLookup;
@@ -1977,6 +1971,8 @@ static class CanMatchContext {
19771971

19781972
private IndexService indexService;
19791973

1974+
private Long timeRangeFilterFromMillis;
1975+
19801976
CanMatchContext(
19811977
ShardSearchRequest request,
19821978
Function<Index, IndexService> indexServiceLookup,
@@ -2024,12 +2020,21 @@ IndexService getIndexService() {
20242020
}
20252021
return this.indexService;
20262022
}
2023+
2024+
void setTimeRangeFilterFromMillis(Long timeRangeFilterFromMillis) {
2025+
this.timeRangeFilterFromMillis = timeRangeFilterFromMillis;
2026+
}
2027+
2028+
Long getTimeRangeFilterFromMillis() {
2029+
return timeRangeFilterFromMillis;
2030+
}
20272031
}
20282032

20292033
static CanMatchShardResponse canMatch(CanMatchContext canMatchContext, boolean checkRefreshPending) {
20302034
assert canMatchContext.request.searchType() == SearchType.QUERY_THEN_FETCH
20312035
: "unexpected search type: " + canMatchContext.request.searchType();
20322036
Releasable releasable = null;
2037+
QueryRewriteContext queryRewriteContext = null;
20332038
try {
20342039
IndexService indexService;
20352040
final boolean hasRefreshPending;
@@ -2042,7 +2047,7 @@ static CanMatchShardResponse canMatch(CanMatchContext canMatchContext, boolean c
20422047
readerContext = canMatchContext.findReaderContext();
20432048
releasable = readerContext.markAsUsed(canMatchContext.getKeepAlive());
20442049
indexService = readerContext.indexService();
2045-
QueryRewriteContext queryRewriteContext = canMatchContext.getQueryRewriteContext(indexService);
2050+
queryRewriteContext = canMatchContext.getQueryRewriteContext(indexService);
20462051
if (queryStillMatchesAfterRewrite(canMatchContext.request, queryRewriteContext) == false) {
20472052
return new CanMatchShardResponse(false, null);
20482053
}
@@ -2051,10 +2056,8 @@ static CanMatchShardResponse canMatch(CanMatchContext canMatchContext, boolean c
20512056
if (canMatchContext.request.readerId().isRetryable() == false) {
20522057
return new CanMatchShardResponse(true, null);
20532058
}
2054-
if (queryStillMatchesAfterRewrite(
2055-
canMatchContext.request,
2056-
canMatchContext.getQueryRewriteContext(canMatchContext.getIndexService())
2057-
) == false) {
2059+
queryRewriteContext = canMatchContext.getQueryRewriteContext(canMatchContext.getIndexService());
2060+
if (queryStillMatchesAfterRewrite(canMatchContext.request, queryRewriteContext) == false) {
20582061
return new CanMatchShardResponse(false, null);
20592062
}
20602063
final Engine.SearcherSupplier searcherSupplier = canMatchContext.getShard().acquireSearcherSupplier();
@@ -2067,10 +2070,8 @@ static CanMatchShardResponse canMatch(CanMatchContext canMatchContext, boolean c
20672070
}
20682071
canMatchSearcher = searcher;
20692072
} else {
2070-
if (queryStillMatchesAfterRewrite(
2071-
canMatchContext.request,
2072-
canMatchContext.getQueryRewriteContext(canMatchContext.getIndexService())
2073-
) == false) {
2073+
queryRewriteContext = canMatchContext.getQueryRewriteContext(canMatchContext.getIndexService());
2074+
if (queryStillMatchesAfterRewrite(canMatchContext.request, queryRewriteContext) == false) {
20742075
return new CanMatchShardResponse(false, null);
20752076
}
20762077
boolean needsWaitForRefresh = canMatchContext.request.waitForCheckpoint() != UNASSIGNED_SEQ_NO;
@@ -2083,6 +2084,7 @@ static CanMatchShardResponse canMatch(CanMatchContext canMatchContext, boolean c
20832084
}
20842085
try (canMatchSearcher) {
20852086
SearchExecutionContext context = canMatchContext.getSearchExecutionContext(canMatchSearcher);
2087+
queryRewriteContext = context;
20862088
final boolean canMatch = queryStillMatchesAfterRewrite(canMatchContext.request, context);
20872089
if (canMatch || hasRefreshPending) {
20882090
FieldSortBuilder sortBuilder = FieldSortBuilder.getPrimaryFieldSortOrNull(canMatchContext.request.source());
@@ -2094,6 +2096,9 @@ static CanMatchShardResponse canMatch(CanMatchContext canMatchContext, boolean c
20942096
} catch (Exception e) {
20952097
return new CanMatchShardResponse(true, null);
20962098
} finally {
2099+
if (queryRewriteContext != null) {
2100+
canMatchContext.setTimeRangeFilterFromMillis(queryRewriteContext.getTimeRangeFilterFromMillis());
2101+
}
20972102
Releasables.close(releasable);
20982103
}
20992104
}

server/src/test/java/org/elasticsearch/search/SearchServiceTests.java

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.elasticsearch.index.fielddata.FieldDataContext;
3232
import org.elasticsearch.index.fielddata.IndexFieldData;
3333
import org.elasticsearch.index.fielddata.LeafFieldData;
34+
import org.elasticsearch.index.mapper.DateFieldMapper;
3435
import org.elasticsearch.index.mapper.KeywordFieldMapper;
3536
import org.elasticsearch.index.mapper.MappedFieldType;
3637
import org.elasticsearch.index.mapper.MapperBuilderContext;
@@ -43,10 +44,12 @@
4344
import org.elasticsearch.index.query.MatchAllQueryBuilder;
4445
import org.elasticsearch.index.query.MatchNoneQueryBuilder;
4546
import org.elasticsearch.index.query.QueryRewriteContext;
47+
import org.elasticsearch.index.query.RangeQueryBuilder;
4648
import org.elasticsearch.index.query.SearchExecutionContext;
4749
import org.elasticsearch.index.shard.IndexShard;
4850
import org.elasticsearch.index.shard.IndexShardTestCase;
4951
import org.elasticsearch.index.shard.ShardId;
52+
import org.elasticsearch.script.ScriptCompiler;
5053
import org.elasticsearch.search.aggregations.support.ValuesSourceType;
5154
import org.elasticsearch.search.builder.SearchSourceBuilder;
5255
import org.elasticsearch.search.internal.AliasFilter;
@@ -61,7 +64,10 @@
6164
import org.elasticsearch.xcontent.XContentParserConfiguration;
6265

6366
import java.io.IOException;
67+
import java.time.LocalDateTime;
68+
import java.time.ZoneOffset;
6469
import java.util.Collections;
70+
import java.util.List;
6571
import java.util.Map;
6672
import java.util.concurrent.CountDownLatch;
6773
import java.util.concurrent.ExecutorService;
@@ -112,6 +118,17 @@ public void testCanMatchKeywordSortedQueryMatchAll() throws IOException {
112118
doTestCanMatch(searchRequest, sortField, true, expectedMinAndMax, false);
113119
}
114120

121+
public void testCanMatchTimeRangeFilter() throws IOException {
122+
SearchRequest searchRequest = new SearchRequest().allowPartialSearchResults(false)
123+
.source(new SearchSourceBuilder().query(new RangeQueryBuilder("@timestamp").from("2025-12-15")));
124+
SearchService.CanMatchContext canMatchContext = doTestCanMatch(searchRequest, null, false, null, false);
125+
LocalDateTime localDateTime = LocalDateTime.of(2025, 12, 15, 0, 0);
126+
assertEquals(
127+
localDateTime.atZone(ZoneOffset.UTC).toInstant().toEpochMilli(),
128+
canMatchContext.getTimeRangeFilterFromMillis().longValue()
129+
);
130+
}
131+
115132
public void testCanMatchKeywordSortedQueryMatchNoneWithException() throws IOException {
116133
SearchRequest searchRequest = new SearchRequest().allowPartialSearchResults(false)
117134
.source(new SearchSourceBuilder().sort("field").query(new MatchNoneQueryBuilder()));
@@ -370,7 +387,7 @@ public void testIsExecutorQueuedBeyondPrewarmingFactor() throws InterruptedExcep
370387
}
371388
}
372389

373-
private void doTestCanMatch(
390+
private SearchService.CanMatchContext doTestCanMatch(
374391
SearchRequest searchRequest,
375392
SortField sortField,
376393
boolean expectedCanMatch,
@@ -392,7 +409,7 @@ private void doTestCanMatch(
392409
IndexShard indexShard = newShard(true);
393410
try {
394411
recoverShardFromStore(indexShard);
395-
assertTrue(indexDoc(indexShard, "_doc", "id", "{\"field\":\"value\"}").isCreated());
412+
assertTrue(indexDoc(indexShard, "_doc", "id", "{\"field\":\"value\", \"@timestamp\":\"2025-12-09\"}").isCreated());
396413
assertTrue(indexShard.refresh("test").refreshed());
397414
try (Engine.Searcher searcher = indexShard.acquireSearcher("test")) {
398415
SearchExecutionContext searchExecutionContext = createSearchExecutionContext(
@@ -416,7 +433,7 @@ private void doTestCanMatch(
416433
assertEquals(expectedMinAndMax.getMin(), minAndMax.getMin());
417434
assertEquals(expectedMinAndMax.getMin(), minAndMax.getMax());
418435
}
419-
436+
return canMatchContext;
420437
}
421438
} finally {
422439
closeShards(indexShard);
@@ -443,9 +460,16 @@ private SearchExecutionContext createSearchExecutionContext(
443460
Collections.emptyMap()
444461
);
445462
KeywordFieldMapper keywordFieldMapper = new KeywordFieldMapper.Builder("field", IndexVersion.current()).build(root);
463+
DateFieldMapper dateFieldMapper = new DateFieldMapper.Builder(
464+
"@timestamp",
465+
DateFieldMapper.Resolution.MILLISECONDS,
466+
null,
467+
ScriptCompiler.NONE,
468+
indexSettings
469+
).build(root);
446470
MappingLookup mappingLookup = MappingLookup.fromMappers(
447471
mapping,
448-
Collections.singletonList(keywordFieldMapper),
472+
List.of(keywordFieldMapper, dateFieldMapper),
449473
Collections.emptyList()
450474
);
451475
return new SearchExecutionContext(

0 commit comments

Comments
 (0)