Skip to content

Commit a78e447

Browse files
javannachrisparrinello
authored andcommitted
Refactor canMatch execution to expose time range filter to caller (elastic#137314)
We recently introduced metrics attributes to track search latencies at the shard and coord level. With elastic#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 f0e5eaa commit a78e447

File tree

3 files changed

+59
-25
lines changed

3 files changed

+59
-25
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 & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1942,10 +1942,11 @@ public void canMatch(CanMatchNodeRequest request, ActionListener<CanMatchNodeRes
19421942
final IndexService indexService = indicesService.indexServiceSafe(shardSearchRequest.shardId().getIndex());
19431943
final IndexShard indexShard = indexService.getShard(shardSearchRequest.shardId().id());
19441944
try {
1945-
// TODO remove the exception handling as it's now in canMatch itself
1946-
responses.add(new CanMatchNodeResponse.ResponseOrFailure(canMatch(shardSearchRequest)));
1947-
indexShard.getSearchOperationListener()
1948-
.onCanMatchPhase(shardSearchRequest, System.nanoTime() - shardCanMatchStartTimeInNanos);
1945+
// TODO remove the exception handling as it's now in canMatch itself - the failure no longer needs to be serialized
1946+
CanMatchContext canMatchContext = createCanMatchContext(shardSearchRequest);
1947+
CanMatchShardResponse canMatchShardResponse = canMatch(canMatchContext, true);
1948+
responses.add(new CanMatchNodeResponse.ResponseOrFailure(canMatchShardResponse));
1949+
indexShard.getSearchOperationListener().onCanMatchPhase(System.nanoTime() - shardCanMatchStartTimeInNanos);
19491950
} catch (Exception e) {
19501951
responses.add(new CanMatchNodeResponse.ResponseOrFailure(e));
19511952
}
@@ -1959,16 +1960,14 @@ public void canMatch(CanMatchNodeRequest request, ActionListener<CanMatchNodeRes
19591960
* won't match any documents on the current shard. Exceptions are handled within the method, and never re-thrown.
19601961
*/
19611962
public CanMatchShardResponse canMatch(ShardSearchRequest request) {
1962-
CanMatchContext canMatchContext = new CanMatchContext(
1963-
request,
1964-
indicesService::indexServiceSafe,
1965-
this::findReaderContext,
1966-
defaultKeepAlive,
1967-
maxKeepAlive
1968-
);
1963+
CanMatchContext canMatchContext = createCanMatchContext(request);
19691964
return canMatch(canMatchContext, true);
19701965
}
19711966

1967+
CanMatchContext createCanMatchContext(ShardSearchRequest request) {
1968+
return new CanMatchContext(request, indicesService::indexServiceSafe, this::findReaderContext, defaultKeepAlive, maxKeepAlive);
1969+
}
1970+
19721971
static class CanMatchContext {
19731972
private final ShardSearchRequest request;
19741973
private final Function<Index, IndexService> indexServiceLookup;
@@ -1978,6 +1977,8 @@ static class CanMatchContext {
19781977

19791978
private IndexService indexService;
19801979

1980+
private Long timeRangeFilterFromMillis;
1981+
19811982
CanMatchContext(
19821983
ShardSearchRequest request,
19831984
Function<Index, IndexService> indexServiceLookup,
@@ -2025,12 +2026,21 @@ IndexService getIndexService() {
20252026
}
20262027
return this.indexService;
20272028
}
2029+
2030+
void setTimeRangeFilterFromMillis(Long timeRangeFilterFromMillis) {
2031+
this.timeRangeFilterFromMillis = timeRangeFilterFromMillis;
2032+
}
2033+
2034+
Long getTimeRangeFilterFromMillis() {
2035+
return timeRangeFilterFromMillis;
2036+
}
20282037
}
20292038

20302039
static CanMatchShardResponse canMatch(CanMatchContext canMatchContext, boolean checkRefreshPending) {
20312040
assert canMatchContext.request.searchType() == SearchType.QUERY_THEN_FETCH
20322041
: "unexpected search type: " + canMatchContext.request.searchType();
20332042
Releasable releasable = null;
2043+
QueryRewriteContext queryRewriteContext = null;
20342044
try {
20352045
IndexService indexService;
20362046
final boolean hasRefreshPending;
@@ -2043,7 +2053,7 @@ static CanMatchShardResponse canMatch(CanMatchContext canMatchContext, boolean c
20432053
readerContext = canMatchContext.findReaderContext();
20442054
releasable = readerContext.markAsUsed(canMatchContext.getKeepAlive());
20452055
indexService = readerContext.indexService();
2046-
QueryRewriteContext queryRewriteContext = canMatchContext.getQueryRewriteContext(indexService);
2056+
queryRewriteContext = canMatchContext.getQueryRewriteContext(indexService);
20472057
if (queryStillMatchesAfterRewrite(canMatchContext.request, queryRewriteContext) == false) {
20482058
return new CanMatchShardResponse(false, null);
20492059
}
@@ -2052,10 +2062,8 @@ static CanMatchShardResponse canMatch(CanMatchContext canMatchContext, boolean c
20522062
if (canMatchContext.request.readerId().isRetryable() == false) {
20532063
return new CanMatchShardResponse(true, null);
20542064
}
2055-
if (queryStillMatchesAfterRewrite(
2056-
canMatchContext.request,
2057-
canMatchContext.getQueryRewriteContext(canMatchContext.getIndexService())
2058-
) == false) {
2065+
queryRewriteContext = canMatchContext.getQueryRewriteContext(canMatchContext.getIndexService());
2066+
if (queryStillMatchesAfterRewrite(canMatchContext.request, queryRewriteContext) == false) {
20592067
return new CanMatchShardResponse(false, null);
20602068
}
20612069
final Engine.SearcherSupplier searcherSupplier = canMatchContext.getShard().acquireSearcherSupplier();
@@ -2068,10 +2076,8 @@ static CanMatchShardResponse canMatch(CanMatchContext canMatchContext, boolean c
20682076
}
20692077
canMatchSearcher = searcher;
20702078
} else {
2071-
if (queryStillMatchesAfterRewrite(
2072-
canMatchContext.request,
2073-
canMatchContext.getQueryRewriteContext(canMatchContext.getIndexService())
2074-
) == false) {
2079+
queryRewriteContext = canMatchContext.getQueryRewriteContext(canMatchContext.getIndexService());
2080+
if (queryStillMatchesAfterRewrite(canMatchContext.request, queryRewriteContext) == false) {
20752081
return new CanMatchShardResponse(false, null);
20762082
}
20772083
boolean needsWaitForRefresh = canMatchContext.request.waitForCheckpoint() != UNASSIGNED_SEQ_NO;
@@ -2084,6 +2090,7 @@ static CanMatchShardResponse canMatch(CanMatchContext canMatchContext, boolean c
20842090
}
20852091
try (canMatchSearcher) {
20862092
SearchExecutionContext context = canMatchContext.getSearchExecutionContext(canMatchSearcher);
2093+
queryRewriteContext = context;
20872094
final boolean canMatch = queryStillMatchesAfterRewrite(canMatchContext.request, context);
20882095
if (canMatch || hasRefreshPending) {
20892096
FieldSortBuilder sortBuilder = FieldSortBuilder.getPrimaryFieldSortOrNull(canMatchContext.request.source());
@@ -2095,6 +2102,9 @@ static CanMatchShardResponse canMatch(CanMatchContext canMatchContext, boolean c
20952102
} catch (Exception e) {
20962103
return new CanMatchShardResponse(true, null);
20972104
} finally {
2105+
if (queryRewriteContext != null) {
2106+
canMatchContext.setTimeRangeFilterFromMillis(queryRewriteContext.getTimeRangeFilterFromMillis());
2107+
}
20982108
Releasables.close(releasable);
20992109
}
21002110
}

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)