Skip to content

Commit 3229dfc

Browse files
authored
Allow efficient can_match phases on frozen indices (#35431)
This change adds a special caching reader that caches all relevant values for a range query to rewrite correctly in a can_match phase without actually opening the underlying directory reader. This allows frozen indices to be filtered with can_match and in-turn searched with wildcards in a efficient way since it allows us to exclude shards that won't match based on their date-ranges without opening their directory readers. Relates to #34352 Depends on #34357
1 parent e81671d commit 3229dfc

File tree

6 files changed

+506
-5
lines changed

6 files changed

+506
-5
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -646,17 +646,17 @@ final SearchContext createContext(ShardSearchRequest request) throws IOException
646646

647647
public DefaultSearchContext createSearchContext(ShardSearchRequest request, TimeValue timeout)
648648
throws IOException {
649-
return createSearchContext(request, timeout, true);
649+
return createSearchContext(request, timeout, true, "search");
650650
}
651651

652652
private DefaultSearchContext createSearchContext(ShardSearchRequest request, TimeValue timeout,
653-
boolean assertAsyncActions)
653+
boolean assertAsyncActions, String source)
654654
throws IOException {
655655
IndexService indexService = indicesService.indexServiceSafe(request.shardId().getIndex());
656656
IndexShard indexShard = indexService.getShard(request.shardId().getId());
657657
SearchShardTarget shardTarget = new SearchShardTarget(clusterService.localNode().getId(),
658658
indexShard.shardId(), request.getClusterAlias(), OriginalIndices.NONE);
659-
Engine.Searcher engineSearcher = indexShard.acquireSearcher("search");
659+
Engine.Searcher engineSearcher = indexShard.acquireSearcher(source);
660660

661661
final DefaultSearchContext searchContext = new DefaultSearchContext(idGenerator.incrementAndGet(), request, shardTarget,
662662
engineSearcher, clusterService, indexService, indexShard, bigArrays, threadPool.estimatedTimeInMillisCounter(), timeout,
@@ -1016,7 +1016,7 @@ public AliasFilter buildAliasFilter(ClusterState state, String index, String...
10161016
*/
10171017
public boolean canMatch(ShardSearchRequest request) throws IOException {
10181018
assert request.searchType() == SearchType.QUERY_THEN_FETCH : "unexpected search type: " + request.searchType();
1019-
try (DefaultSearchContext context = createSearchContext(request, defaultSearchTimeout, false)) {
1019+
try (DefaultSearchContext context = createSearchContext(request, defaultSearchTimeout, false, "can_match")) {
10201020
SearchSourceBuilder source = context.request().source();
10211021
if (canRewriteToMatchNone(source)) {
10221022
QueryBuilder queryBuilder = source.query();

x-pack/plugin/core/src/main/java/org/elasticsearch/index/engine/FrozenEngine.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.apache.lucene.search.IndexSearcher;
2929
import org.apache.lucene.search.ReferenceManager;
3030
import org.apache.lucene.store.AlreadyClosedException;
31+
import org.apache.lucene.store.Directory;
3132
import org.apache.lucene.util.Bits;
3233
import org.elasticsearch.common.SuppressForbidden;
3334
import org.elasticsearch.common.lucene.Lucene;
@@ -40,6 +41,7 @@
4041
import java.io.IOException;
4142
import java.io.UncheckedIOException;
4243
import java.util.List;
44+
import java.util.concurrent.CountDownLatch;
4345
import java.util.function.Function;
4446

4547
/**
@@ -66,9 +68,23 @@ public final class FrozenEngine extends ReadOnlyEngine {
6668
public static final Setting<Boolean> INDEX_FROZEN = Setting.boolSetting("index.frozen", false, Setting.Property.IndexScope,
6769
Setting.Property.PrivateIndex);
6870
private volatile DirectoryReader lastOpenedReader;
71+
private final DirectoryReader canMatchReader;
6972

7073
public FrozenEngine(EngineConfig config) {
7174
super(config, null, null, true, Function.identity());
75+
76+
boolean success = false;
77+
Directory directory = store.directory();
78+
try (DirectoryReader reader = DirectoryReader.open(directory)) {
79+
canMatchReader = new RewriteCachingDirectoryReader(directory, reader.leaves());
80+
success = true;
81+
} catch (IOException e) {
82+
throw new UncheckedIOException(e);
83+
} finally {
84+
if (success == false) {
85+
closeNoLock("failed on construction", new CountDownLatch(1));
86+
}
87+
}
7288
}
7389

7490
@Override
@@ -193,6 +209,7 @@ public Searcher acquireSearcher(String source, SearcherScope scope) throws Engin
193209
case "segments_stats":
194210
case "completion_stats":
195211
case "refresh_needed":
212+
case "can_match": // special case for can_match phase - we use the cached point values reader
196213
maybeOpenReader = false;
197214
break;
198215
default:
@@ -205,6 +222,10 @@ public Searcher acquireSearcher(String source, SearcherScope scope) throws Engin
205222
// we just hand out a searcher on top of an empty reader that we opened for the ReadOnlyEngine in the #open(IndexCommit)
206223
// method. this is the case when we don't have a reader open right now and we get a stats call any other that falls in
207224
// the category that doesn't trigger a reopen
225+
if ("can_match".equals(source)) {
226+
canMatchReader.incRef();
227+
return new Searcher(source, new IndexSearcher(canMatchReader), canMatchReader::decRef);
228+
}
208229
return super.acquireSearcher(source, scope);
209230
} else {
210231
try {
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
package org.elasticsearch.index.engine;
7+
8+
import org.apache.lucene.index.BinaryDocValues;
9+
import org.apache.lucene.index.DirectoryReader;
10+
import org.apache.lucene.index.FieldInfo;
11+
import org.apache.lucene.index.FieldInfos;
12+
import org.apache.lucene.index.Fields;
13+
import org.apache.lucene.index.IndexCommit;
14+
import org.apache.lucene.index.IndexWriter;
15+
import org.apache.lucene.index.LeafMetaData;
16+
import org.apache.lucene.index.LeafReader;
17+
import org.apache.lucene.index.LeafReaderContext;
18+
import org.apache.lucene.index.NumericDocValues;
19+
import org.apache.lucene.index.PointValues;
20+
import org.apache.lucene.index.SortedDocValues;
21+
import org.apache.lucene.index.SortedNumericDocValues;
22+
import org.apache.lucene.index.SortedSetDocValues;
23+
import org.apache.lucene.index.StoredFieldVisitor;
24+
import org.apache.lucene.index.Terms;
25+
import org.apache.lucene.store.Directory;
26+
import org.apache.lucene.util.Bits;
27+
28+
import java.io.IOException;
29+
import java.util.HashMap;
30+
import java.util.List;
31+
import java.util.Map;
32+
33+
/**
34+
* This special DirectoryReader is used to handle can_match requests against frozen indices.
35+
* It' caches all relevant point value data for every point value field ie. min/max packed values etc.
36+
* to hold enough information to rewrite a date range query and make a decisions if an index can match or not.
37+
* This allows frozen indices to be searched with wildcards in a very efficient way without opening a reader on them.
38+
*/
39+
final class RewriteCachingDirectoryReader extends DirectoryReader {
40+
41+
RewriteCachingDirectoryReader(Directory directory, List<LeafReaderContext> segmentReaders) throws
42+
IOException {
43+
super(directory, wrap(segmentReaders));
44+
}
45+
46+
private static LeafReader[] wrap(List<LeafReaderContext> readers) throws IOException {
47+
LeafReader[] wrapped = new LeafReader[readers.size()];
48+
int i = 0;
49+
for (LeafReaderContext ctx : readers) {
50+
LeafReader wrap = new RewriteCachingLeafReader(ctx.reader());
51+
wrapped[i++] = wrap;
52+
}
53+
return wrapped;
54+
}
55+
56+
@Override
57+
protected DirectoryReader doOpenIfChanged() {
58+
throw new UnsupportedOperationException();
59+
}
60+
61+
@Override
62+
protected DirectoryReader doOpenIfChanged(IndexCommit commit) {
63+
throw new UnsupportedOperationException();
64+
}
65+
66+
@Override
67+
protected DirectoryReader doOpenIfChanged(IndexWriter writer, boolean applyAllDeletes) {
68+
throw new UnsupportedOperationException();
69+
}
70+
71+
@Override
72+
public long getVersion() {
73+
throw new UnsupportedOperationException();
74+
}
75+
76+
@Override
77+
public boolean isCurrent() {
78+
throw new UnsupportedOperationException();
79+
}
80+
81+
@Override
82+
public IndexCommit getIndexCommit() {
83+
throw new UnsupportedOperationException();
84+
}
85+
86+
@Override
87+
protected void doClose() {
88+
throw new UnsupportedOperationException();
89+
}
90+
91+
@Override
92+
public CacheHelper getReaderCacheHelper() {
93+
throw new UnsupportedOperationException();
94+
}
95+
96+
// except of a couple of selected methods everything else will
97+
// throw a UOE which causes a can_match phase to just move to the actual phase
98+
// later such that we never false exclude a shard if something else is used to rewrite.
99+
private static final class RewriteCachingLeafReader extends LeafReader {
100+
101+
private final int maxDoc;
102+
private final int numDocs;
103+
private final Map<String, PointValues> pointValuesMap;
104+
private final FieldInfos fieldInfos;
105+
106+
private RewriteCachingLeafReader(LeafReader original) throws IOException {
107+
this.maxDoc = original.maxDoc();
108+
this.numDocs = original.numDocs();
109+
fieldInfos = original.getFieldInfos();
110+
Map<String, PointValues> valuesMap = new HashMap<>();
111+
for (FieldInfo info : fieldInfos) {
112+
if (info.getPointIndexDimensionCount() != 0) {
113+
PointValues pointValues = original.getPointValues(info.name);
114+
if (pointValues != null) { // might not be in this reader
115+
byte[] minPackedValue = pointValues.getMinPackedValue();
116+
byte[] maxPackedValue = pointValues.getMaxPackedValue();
117+
int numDimensions = pointValues.getNumIndexDimensions();
118+
int bytesPerDimension = pointValues.getBytesPerDimension();
119+
int numDataDimensions = pointValues.getNumDataDimensions();
120+
long size = pointValues.size();
121+
int docCount = pointValues.getDocCount();
122+
valuesMap.put(info.name, new PointValues() {
123+
@Override
124+
public void intersect(IntersectVisitor visitor) {
125+
throw new UnsupportedOperationException();
126+
}
127+
128+
@Override
129+
public long estimatePointCount(IntersectVisitor visitor) {
130+
throw new UnsupportedOperationException();
131+
}
132+
133+
@Override
134+
public byte[] getMinPackedValue() {
135+
return minPackedValue;
136+
}
137+
138+
@Override
139+
public byte[] getMaxPackedValue() {
140+
return maxPackedValue;
141+
}
142+
143+
@Override
144+
public int getNumDataDimensions() {
145+
return numDataDimensions;
146+
}
147+
148+
@Override
149+
public int getNumIndexDimensions() {
150+
return numDimensions;
151+
}
152+
153+
@Override
154+
public int getBytesPerDimension() {
155+
return bytesPerDimension;
156+
}
157+
158+
@Override
159+
public long size() {
160+
return size;
161+
}
162+
163+
@Override
164+
public int getDocCount() {
165+
return docCount;
166+
}
167+
});
168+
}
169+
}
170+
}
171+
pointValuesMap = valuesMap;
172+
}
173+
174+
@Override
175+
public CacheHelper getCoreCacheHelper() {
176+
throw new UnsupportedOperationException();
177+
}
178+
179+
@Override
180+
public Terms terms(String field) {
181+
throw new UnsupportedOperationException();
182+
}
183+
184+
@Override
185+
public NumericDocValues getNumericDocValues(String field) {
186+
throw new UnsupportedOperationException();
187+
}
188+
189+
@Override
190+
public BinaryDocValues getBinaryDocValues(String field) {
191+
throw new UnsupportedOperationException();
192+
}
193+
194+
@Override
195+
public SortedDocValues getSortedDocValues(String field) {
196+
throw new UnsupportedOperationException();
197+
}
198+
199+
@Override
200+
public SortedNumericDocValues getSortedNumericDocValues(String field) {
201+
throw new UnsupportedOperationException();
202+
}
203+
204+
@Override
205+
public SortedSetDocValues getSortedSetDocValues(String field) {
206+
throw new UnsupportedOperationException();
207+
}
208+
209+
@Override
210+
public NumericDocValues getNormValues(String field) {
211+
throw new UnsupportedOperationException();
212+
}
213+
214+
@Override
215+
public FieldInfos getFieldInfos() {
216+
return fieldInfos;
217+
}
218+
219+
@Override
220+
public Bits getLiveDocs() {
221+
throw new UnsupportedOperationException();
222+
}
223+
224+
@Override
225+
public PointValues getPointValues(String field) {
226+
return pointValuesMap.get(field);
227+
}
228+
229+
@Override
230+
public void checkIntegrity() {
231+
}
232+
233+
@Override
234+
public LeafMetaData getMetaData() {
235+
throw new UnsupportedOperationException();
236+
}
237+
238+
@Override
239+
public Fields getTermVectors(int docID) {
240+
throw new UnsupportedOperationException();
241+
}
242+
243+
@Override
244+
public int numDocs() {
245+
return numDocs;
246+
}
247+
248+
@Override
249+
public int maxDoc() {
250+
return maxDoc;
251+
}
252+
253+
@Override
254+
public void document(int docID, StoredFieldVisitor visitor) {
255+
throw new UnsupportedOperationException();
256+
}
257+
258+
@Override
259+
protected void doClose() {
260+
}
261+
262+
@Override
263+
public CacheHelper getReaderCacheHelper() {
264+
return null;
265+
}
266+
}
267+
}

0 commit comments

Comments
 (0)