Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lucene/CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ Optimizations
---------------------
* GITHUG#16280: Single-pass writeString fast path for short strings in ByteBuffersDataOutput (neoremind)

* GITHUB#16220: Implement WANDScorer.advanceShallow() to enable block-max optimizations when nested in conjunctions. (Prithvi S)

* GITHUB#16146: Speed up no-score filtered-optional Boolean queries by routing dense conjunctions with cached FixedBitSet filters
through ConstantScoreBulkScorer, which batches matches in bulk via DocIdStream. (Costin Leau)

Expand Down
17 changes: 9 additions & 8 deletions lucene/core/src/java/org/apache/lucene/search/WANDScorer.java
Original file line number Diff line number Diff line change
Expand Up @@ -566,17 +566,18 @@ public float score() throws IOException {

@Override
public int advanceShallow(int target) throws IOException {
// Propagate to improve score bounds
// Propagate to sub-scorers. Scorers past target constrain the boundary to docID - 1.
int newUpTo = DocIdSetIterator.NO_MORE_DOCS;
for (Scorer scorer : allScorers) {
if (scorer.docID() < target) {
scorer.advanceShallow(target);
// Use <= instead of < so we still propagate and use the block boundary when already at
// target.
if (scorer.docID() <= target) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the old code used < target (skip propagation when already at target), this uses <= target. The <= is correct here since you need the return value from advanceShallow even when docID() == target, but worth a one-line comment explaining the change from < to <= (vs a typo).

newUpTo = Math.min(newUpTo, scorer.advanceShallow(target));
} else if (scorer.docID() != DocIdSetIterator.NO_MORE_DOCS) {
newUpTo = Math.min(newUpTo, scorer.docID() - 1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider return (target <= upTo) ? Math.min(upTo, newUpTo) : newUpTo;

Otherwise the early return discards the freshly computed newUpTo, which could be tighter than upTo.
Always returning the previous upTo is safe (advanceShallow contract allows over-approximation), but it means the parent gets a looser bound than necessary hence the suggested conditional.

}
}
if (target <= upTo) {
return upTo;
}
// TODO: implement
return DocIdSetIterator.NO_MORE_DOCS;
return newUpTo;
}

@Override
Expand Down
47 changes: 47 additions & 0 deletions lucene/core/src/test/org/apache/lucene/search/TestWANDScorer.java
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,53 @@ private void doTestRandomSpecialMaxScore(float maxScore) throws IOException {
dir.close();
}

/** Test advanceShallow when WAND is nested inside a conjunction with BlockScoreQueryWrapper. */
public void testRandomNestedWAND() throws IOException {
Directory dir = newDirectory();
IndexWriter w = new IndexWriter(dir, newIndexWriterConfig());
int numDocs = atLeast(1000);
for (int i = 0; i < numDocs; ++i) {
Document doc = new Document();
int numValues = random().nextInt(1 << random().nextInt(5));
int start = random().nextInt(10);
for (int j = 0; j < numValues; ++j) {
doc.add(new StringField("foo", Integer.toString(start + j), Store.NO));
}
w.addDocument(doc);
}
IndexReader reader = DirectoryReader.open(w);
w.close();
// turn off concurrent search to avoid Random object used across threads resulting into
// RuntimeException, as WANDScorerQuery#createWeight has reference to this searcher,
// but will be called during searching
IndexSearcher searcher = newSearcher(reader, true, true, false);

for (int iter = 0; iter < 10; ++iter) {
int start = random().nextInt(10);
int numClauses = random().nextInt(1 << random().nextInt(5));
BooleanQuery.Builder builder = new BooleanQuery.Builder();
for (int i = 0; i < numClauses; ++i) {
builder.add(
maybeWrap(new TermQuery(new Term("foo", Integer.toString(start + i)))), Occur.SHOULD);
}
Query wandQuery = new WANDScorerQuery(builder.build(), random().nextBoolean());

int blockLength = TestUtil.nextInt(random(), 2, 16);
Query wrappedWand = new BlockScoreQueryWrapper(wandQuery, blockLength);

int filterTerm = random().nextInt(30);
Query nestedQuery =
new BooleanQuery.Builder()
.add(wrappedWand, Occur.MUST)
.add(new TermQuery(new Term("foo", Integer.toString(filterTerm))), Occur.MUST)
.build();

CheckHits.checkTopScores(random(), nestedQuery, searcher);
}
reader.close();
dir.close();
}

private static class MaxScoreWrapperScorer extends FilterScorer {

private final int maxRange;
Expand Down
Loading