Skip to content

Commit 9d09aa7

Browse files
Fix VectorScorer#bulk eagerly advancing its iterator (#16377)
The default bulk() implementation advanced its iterator as a side effect of construction, unlike Bulk.fromRandomScorerDense/Sparse, which defer positioning to the first nextDocsAndScores call. Move the positioning into the returned Bulk to match, and update the javadoc. The current behavior is problematic when combining the iterator with others via conjunction. Given that this API is evolving and the behavior is split I propose adopting the lazy behavior. In one case, I hit the following error: Caused by: java.lang.IllegalArgumentException: Sub-iterators of ConjunctionDISI are not on the same document! at ConjunctionDISI.throwSubIteratorsNotOnSameDocument(ConjunctionDISI.java:149) at ConjunctionDISI.createConjunction(ConjunctionDISI.java:104) at ConjunctionUtils.intersectScorers(ConjunctionUtils.java:44) at ConjunctionScorer.<init>(ConjunctionScorer.java:36) at BooleanScorerSupplier.requiredBulkScorer(BooleanScorerSupplier.java:453) at BooleanScorerSupplier.booleanScorer(BooleanScorerSupplier.java:219) at BooleanScorerSupplier.bulkScorer(BooleanScorerSupplier.java:177) The tests don't hit this error because it's trivial to confirm that the bulk call is side effect free.
1 parent 99fc6a1 commit 9d09aa7

3 files changed

Lines changed: 39 additions & 5 deletions

File tree

lucene/CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,8 @@ Bug Fixes
224224

225225
* GITHUB#16303: Add suppressed exception details when lock acquisition fails in NativeFSLockFactory. (Bharathi-Kanna)
226226

227+
* Make VectorScorer#bulk default method lazily position its iterator on first use. (Michael Marshall)
228+
227229
Changes in Runtime Behavior
228230
---------------------
229231
* GITHUB#14187: The query cache is now disabled by default. (Adrien Grand)

lucene/core/src/java/org/apache/lucene/search/VectorScorer.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ public interface VectorScorer {
5050
* docs. The iterator of this instance of VectorScorer should be used and iterated in conjunction
5151
* with the provided matchingDocs iterator to score only the documents that are present in both
5252
* iterators. If the provided matchingDocs iterator is null, then all documents should be scored.
53-
* Additionally, if the iterators are unpositioned (docID() == -1), this method should position
54-
* them to the first document.
53+
* Additionally, if the iterators are unpositioned (docID() == -1), the first call to {@link
54+
* Bulk#nextDocsAndScores} should position them to the first document.
5555
*
5656
* @param matchingDocs Optional filter to iterate over the documents to score
5757
* @return a {@link Bulk} scorer
@@ -63,11 +63,11 @@ default Bulk bulk(DocIdSetIterator matchingDocs) throws IOException {
6363
matchingDocs == null
6464
? iterator()
6565
: ConjunctionUtils.createConjunction(List.of(matchingDocs, iterator()), List.of());
66-
if (iterator.docID() == -1) {
67-
iterator.nextDoc();
68-
}
6966
return (upTo, liveDocs, buffer) -> {
7067
assert upTo > 0;
68+
if (iterator.docID() == -1) {
69+
iterator.nextDoc();
70+
}
7171
buffer.growNoCopy(DEFAULT_BULK_BATCH_SIZE);
7272
int size = 0;
7373
float maxScore = Float.NEGATIVE_INFINITY;

lucene/core/src/test/org/apache/lucene/search/TestVectorScorer.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import org.apache.lucene.store.Directory;
3333
import org.apache.lucene.tests.index.RandomIndexWriter;
3434
import org.apache.lucene.tests.util.LuceneTestCase;
35+
import org.apache.lucene.util.BitSetIterator;
36+
import org.apache.lucene.util.FixedBitSet;
3537

3638
public class TestVectorScorer extends LuceneTestCase {
3739

@@ -64,6 +66,36 @@ public void testFindAll() throws IOException {
6466
}
6567
}
6668

69+
public void testBulkDoesNotEagerlyAdvanceIterator() throws IOException {
70+
FixedBitSet bits = new FixedBitSet(10);
71+
bits.set(2);
72+
bits.set(5);
73+
bits.set(7);
74+
BitSetIterator sharedIterator = new BitSetIterator(bits, bits.cardinality());
75+
VectorScorer scorer =
76+
new VectorScorer() {
77+
@Override
78+
public float score() {
79+
return 1f;
80+
}
81+
82+
@Override
83+
public DocIdSetIterator iterator() {
84+
return sharedIterator;
85+
}
86+
};
87+
88+
assertEquals(-1, scorer.iterator().docID());
89+
scorer.bulk(null); // constructing a Bulk must not have any observable side effect
90+
assertEquals(-1, scorer.iterator().docID());
91+
92+
FixedBitSet matchBits = new FixedBitSet(10);
93+
matchBits.set(2);
94+
matchBits.set(7);
95+
scorer.bulk(new BitSetIterator(matchBits, matchBits.cardinality()));
96+
assertEquals(-1, scorer.iterator().docID());
97+
}
98+
6799
/** Creates a new directory and adds documents with the given vectors as kNN vector fields */
68100
private Directory getIndexStore(String field, VectorEncoding encoding, float[]... contents)
69101
throws IOException {

0 commit comments

Comments
 (0)