Skip to content

Commit a16bca0

Browse files
authored
Follow up apache#16061. Tighten score supplier cost estimation and introduce SortedSkipperScorerSupplier (apache#16070)
1 parent c2825cf commit a16bca0

4 files changed

Lines changed: 222 additions & 131 deletions

File tree

lucene/CHANGES.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,8 +376,8 @@ Optimizations
376376

377377
* GITHUB#16001: IndexSearcher.count() was calling query.rewrite twice, a regression since v9.10 (David Smiley)
378378

379-
* GITHUB16061#: Improve cost estimation in SortedSetDocValuesRangeQuery when using DocValuesSkipper and the field
380-
is dense and is the primary sort of the index and reduce the number of doc values visited. (Ignacio Vera)
379+
* GITHUB#16061, GITHUB#16070: Improve cost estimation in SortedSetDocValuesRangeQuery when using DocValuesSkipper
380+
and the field is dense and is the primary sort of the index to reduce the number of doc values visited. (Ignacio Vera)
381381

382382

383383
Bug Fixes

lucene/core/src/java/org/apache/lucene/document/SortedSetDocValuesRangeQuery.java

Lines changed: 33 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.apache.lucene.document;
1818

1919
import java.io.IOException;
20-
import java.io.UncheckedIOException;
2120
import java.util.Objects;
2221
import java.util.function.LongPredicate;
2322
import org.apache.lucene.index.DocValues;
@@ -37,6 +36,7 @@
3736
import org.apache.lucene.search.ScoreMode;
3837
import org.apache.lucene.search.ScorerSupplier;
3938
import org.apache.lucene.search.Sort;
39+
import org.apache.lucene.search.SortField;
4040
import org.apache.lucene.search.TwoPhaseIterator;
4141
import org.apache.lucene.search.Weight;
4242
import org.apache.lucene.util.BytesRef;
@@ -121,8 +121,12 @@ public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOExcepti
121121
DocValuesSkipper skipper = context.reader().getDocValuesSkipper(field);
122122
SortedSetDocValues values = DocValues.getSortedSet(context.reader(), field);
123123
final SortedDocValues singleton = DocValues.unwrapSingleton(values);
124-
if (singleton != null && skipper != null && isDensePrimarySort(context.reader(), skipper)) {
125-
return getScorerSupplierFromDensePrimarySort(context, singleton, values, skipper);
124+
final SortField primarySortField;
125+
if (singleton != null
126+
&& skipper != null
127+
&& (primarySortField = densePrimarySort(context.reader(), skipper)) != null) {
128+
return getScorerSupplierFromDensePrimarySort(
129+
context, singleton, values, skipper, primarySortField);
126130
}
127131
// implement ScorerSupplier, since we do some expensive stuff to make a scorer
128132
return new ConstantScoreScorerSupplier(score(), scoreMode, context.reader().maxDoc()) {
@@ -166,121 +170,40 @@ private ScorerSupplier getScorerSupplierFromDensePrimarySort(
166170
LeafReaderContext context,
167171
SortedDocValues singleton,
168172
SortedSetDocValues values,
169-
DocValuesSkipper skipper) {
170-
final Sort indexSort = context.reader().getMetaData().sort();
171-
return new ConstantScoreScorerSupplier(score(), scoreMode, context.reader().maxDoc()) {
172-
int skipperMinDocId = -1, skipperMaxDocId = -1;
173-
long minOrd, maxOrd;
174-
boolean skipperMinDocIdExact = false, skipperMaxDocIdExact = false;
173+
DocValuesSkipper skipper,
174+
SortField sortField) {
175+
return new SortedSkipperScorerSupplier(
176+
skipper, sortField, score(), scoreMode, context.reader().maxDoc()) {
177+
long minOrd = -1, maxOrd = -1;
175178

176179
@Override
177-
public DocIdSetIterator iterator(long leadCost) throws IOException {
178-
if (skipperMinDocId == -1) {
179-
computeSkipperDocIds();
180-
}
181-
final int minDocID;
182-
final int maxDocID;
183-
if (indexSort.getSort()[0].getReverse()) {
184-
minDocID =
185-
skipperMinDocIdExact
186-
? skipperMinDocId
187-
: nextDoc(skipperMinDocId, singleton, l -> l <= maxOrd);
188-
maxDocID =
189-
skipperMaxDocIdExact
190-
? skipperMaxDocId
191-
: nextDoc(skipperMaxDocId, singleton, l -> l < minOrd);
192-
} else {
193-
minDocID =
194-
skipperMinDocIdExact
195-
? skipperMinDocId
196-
: nextDoc(skipperMinDocId, singleton, l -> l >= minOrd);
197-
maxDocID =
198-
skipperMaxDocIdExact
199-
? skipperMaxDocId
200-
: nextDoc(skipperMaxDocId, singleton, l -> l > maxOrd);
180+
protected long getLowerValue() throws IOException {
181+
if (minOrd == -1) {
182+
minOrd = minOrd(values);
201183
}
202-
return minDocID == maxDocID
203-
? DocIdSetIterator.empty()
204-
: DocIdSetIterator.range(minDocID, maxDocID);
184+
return minOrd;
205185
}
206186

207187
@Override
208-
public long cost() {
209-
if (skipperMinDocId == -1) {
210-
try {
211-
// Similar to PointValues, IOExceptions needs to be caught and rethrown as
212-
// UncheckedIOException
213-
computeSkipperDocIds();
214-
} catch (IOException e) {
215-
throw new UncheckedIOException(e);
216-
}
188+
protected long getUpperValue() throws IOException {
189+
if (maxOrd == -1) {
190+
maxOrd = maxOrd(values);
217191
}
218-
if (skipperMinDocIdExact && skipperMaxDocIdExact) {
219-
return skipperMaxDocId - skipperMinDocId;
220-
}
221-
// TODO: expose skipper block size here?
222-
return Math.min(context.reader().maxDoc(), 4096 + skipperMaxDocId - skipperMinDocId);
192+
return maxOrd;
223193
}
224194

225-
private void computeSkipperDocIds() throws IOException {
226-
minOrd = minOrd(values);
227-
maxOrd = upperValue != null && upperValue.equals(lowerValue) ? minOrd : maxOrd(values);
228-
if (minOrd > maxOrd || minOrd > skipper.maxValue() || maxOrd < skipper.minValue()) {
229-
skipperMinDocId = skipperMaxDocId = DocIdSetIterator.NO_MORE_DOCS;
230-
skipperMinDocIdExact = skipperMaxDocIdExact = true;
231-
return;
232-
}
233-
if (skipper.minValue() >= minOrd && skipper.maxValue() <= maxOrd) {
234-
skipperMinDocId = 0;
235-
skipperMaxDocId = skipper.docCount();
236-
skipperMinDocIdExact = skipperMaxDocIdExact = true;
237-
return;
195+
@Override
196+
protected int nextDoc(int startDocId, LongPredicate predicate) throws IOException {
197+
int doc = singleton.docID();
198+
if (startDocId > doc) {
199+
doc = singleton.advance(startDocId);
238200
}
239-
if (indexSort.getSort()[0].getReverse()) {
240-
if (skipper.maxValue() <= maxOrd) {
241-
skipperMinDocId = 0;
242-
skipperMinDocIdExact = true;
243-
} else {
244-
skipper.advance(Long.MIN_VALUE, maxOrd);
245-
skipperMinDocId = skipper.minDocID(0);
246-
skipperMinDocIdExact = skipper.maxValue(0) == maxOrd;
247-
}
248-
if (skipper.minValue() >= minOrd) {
249-
skipperMaxDocId = skipper.docCount();
250-
skipperMaxDocIdExact = true;
251-
} else {
252-
skipper.advance(Long.MIN_VALUE, minOrd);
253-
skipperMaxDocId =
254-
skipper.minValue(0) == minOrd ? skipper.maxDocID(0) + 1 : skipper.minDocID(0);
255-
// we can read the next block, if the maxValue is different to minOrd, then we
256-
// should be done, we
257-
// don't need to visit the doc values. But what is more expensive, visit one doc
258-
// value or one skipper block?
259-
skipperMaxDocIdExact = false;
260-
}
261-
} else {
262-
if (skipper.minValue() >= minOrd) {
263-
skipperMinDocId = 0;
264-
skipperMinDocIdExact = true;
265-
} else {
266-
skipper.advance(minOrd, Long.MAX_VALUE);
267-
skipperMinDocId = skipper.minDocID(0);
268-
skipperMinDocIdExact = skipper.minValue(0) == minOrd;
269-
}
270-
if (skipper.maxValue() <= maxOrd) {
271-
skipperMaxDocId = skipper.docCount();
272-
skipperMaxDocIdExact = true;
273-
} else {
274-
skipper.advance(maxOrd, Long.MAX_VALUE);
275-
skipperMaxDocId =
276-
skipper.maxValue(0) == maxOrd ? skipper.maxDocID(0) + 1 : skipper.minDocID(0);
277-
// we can read the next block, if the minValue is different to maxOrd, then we
278-
// should be done, we
279-
// don't need to visit the doc values. But what is more expensive, visit one doc
280-
// value or one skipper block?
281-
skipperMaxDocIdExact = false;
201+
for (; doc < DocIdSetIterator.NO_MORE_DOCS; doc = singleton.nextDoc()) {
202+
if (predicate.test(singleton.ordValue())) {
203+
break;
282204
}
283205
}
206+
return doc;
284207
}
285208
};
286209
}
@@ -326,30 +249,16 @@ private long maxOrd(SortedSetDocValues values) throws IOException {
326249
return maxOrd;
327250
}
328251

329-
private boolean isDensePrimarySort(LeafReader reader, DocValuesSkipper skipper) {
252+
private SortField densePrimarySort(LeafReader reader, DocValuesSkipper skipper) {
330253
if (skipper.docCount() != reader.maxDoc()) {
331-
return false;
254+
return null;
332255
}
333256
final Sort indexSort = reader.getMetaData().sort();
334257
if (indexSort == null
335258
|| indexSort.getSort().length == 0
336259
|| indexSort.getSort()[0].getField().equals(field) == false) {
337-
return false;
338-
}
339-
return true;
340-
}
341-
342-
private static int nextDoc(int startDoc, SortedDocValues docValues, LongPredicate predicate)
343-
throws IOException {
344-
int doc = docValues.docID();
345-
if (startDoc > doc) {
346-
doc = docValues.advance(startDoc);
347-
}
348-
for (; doc < DocIdSetIterator.NO_MORE_DOCS; doc = docValues.nextDoc()) {
349-
if (predicate.test(docValues.ordValue())) {
350-
break;
351-
}
260+
return null;
352261
}
353-
return doc;
262+
return indexSort.getSort()[0];
354263
}
355264
}

0 commit comments

Comments
 (0)