Enable filtered search on mutable HNSW indexes - #19336
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #19336 +/- ##
============================================
- Coverage 67.54% 67.52% -0.02%
Complexity 1430 1430
============================================
Files 3486 3486
Lines 224043 224105 +62
Branches 35353 35365 +12
============================================
- Hits 151339 151338 -1
- Misses 60674 60744 +70
+ Partials 12030 12023 -7
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
0e21fdf to
916f8c5
Compare
MutableVectorIndex could not restrict its search to a document set, so every vector query on a consuming segment of an upsert table fell back to a brute-force exact scan over the forward index. Implement FilterAwareVectorIndexReader so those queries use filtered ANN instead. - Store the Pinot document ID as a numeric doc value rather than a stored field, so it can drive filtered traversal and translate hits back from the same reader generation used for search. - Take the document ID from the caller instead of an internal counter. MutableIndex.add documents that rows may arrive in any order, so the counter produced wrong IDs under out-of-order ingestion. - Open the reader from the writer so completed additions are searchable without waiting for a commit. - Give each instance its own temporary directory, so replicas of the same segment hosted in one JVM cannot collide on a Lucene write lock. No planner change is needed: FilterPlanNode already routes a required candidate document set to filtered ANN whenever the reader advertises the capability, and to the exact scan when it does not.
916f8c5 to
e4ab80c
Compare
Review — mutable HNSW filtered searchReviewed at What is good
1. (Major) NRT reading has no publication bound
An earlier revision of this PR bounded exactly this, via Where it bites: on a non-upsert realtime table Suggested fix: no bitmap needed. Capture the watermark and drop hits at or above it in 2. (Major) Duplicates a filter query that already exists
#19303 already solves this: it extracts 3. (Major) An NRT reader is opened on every search
4. (Minor) Temporary directories lose attribution
🤖 Automated review by Claude Code |
|
Superseded by #19303, closing. #19303 now covers the same capability — making the mutable HNSW index filter-aware so consuming segments of upsert tables use filtered ANN instead of the exact-scan fallback — rebased onto master after #19297–#19301 landed, so it no longer carries the base-fix half this PR was stacked on. The two ideas from here that were worth keeping are folded in:
Two things were deliberately not carried over. This branch reimplements the Lucene filter iterator privately, while Also noted for whoever revisits this area: the |
Summary
MutableVectorIndexcould not restrict its search to a document set, so every vectorquery on a consuming segment of an upsert table fell back to a brute-force exact scan
over the forward index. This implements
FilterAwareVectorIndexReaderso those queriesuse filtered ANN instead.
can drive filtered graph traversal and translate hits back from the same reader
generation used for search.
MutableIndex.adddocuments that rows may arrive in any order, so the counter produced wrong document IDs
under out-of-order ingestion — a latent bug independent of filtering.
for a commit.
in one JVM cannot collide on a Lucene write lock. The segment and column stay in the
directory name so one left behind by a crashed process can still be attributed.
No planner change is required.
FilterPlanNodealready routes a required candidatedocument set to filtered ANN whenever the reader advertises the capability, and to
ExactVectorScanFilterOperatorwhen it does not, so this change alone flips consumingsegments of upsert tables off the exact-scan path.
Rebased
This was a cumulative draft stacked on #19287. That work has since landed as
#19297, #19298, #19299 and #19300, so this branch is now rebased directly onto
masterand contains only the mutable-index layer: 2 files, +383/-31, down from 15 files.
Dropped as obsolete during the rebase:
pinot-coreplumbing (VectorCandidateScope,FilterPlanNode,VectorSimilarityFilterOperator,ExactVectorScanFilterOperator,VectorSearchStrategy) — superseded by the merged design, which reaches the sameoutcome with no changes here;
org.jetbrains:annotationsdependency added topinot-segment-local, which wasunused (the module compiles without it).
Publication boundary
Opening the reader from the writer makes rows visible to search as soon as they are
added, which is slightly ahead of when the segment publishes them to queries (a row is
added to the indexes, then the document count is raised). Documents past that boundary
cannot produce wrong data:
BitmapDocIdIteratorstops at the segment'snumDocs, sothey are never read. The residual effect is that such a document can occupy a top-K slot
and then be dropped, so a query against a consuming segment can return fewer than K rows
within that window.
The window is narrow and the same shape as the pre-existing one (a commit could already
land between a row being added and the count being raised), but it is wider now.
Bounding candidate traversal by the published watermark would close it; testing
pinotDocId < numPublishedDocsinside the filter iterator is O(1) per candidate andneeds no allocation, unlike materializing a dense bitmap per query.
Follow-ups worth considering
HnswVectorIndexReader.RoaringBitmapFilterQueryinstead of thesecond filter-query implementation added here; extracting a shared base would keep the
two from drifting.
SearcherManager/ReaderManagerwithmaybeRefresh()is the usual pattern and is worth benchmarkingnow that this is the default path for realtime vector queries.
Validation
MutableVectorIndexTest: 10 tests passed, covering supplied document IDs, filteredexclusion of nearer disallowed documents, near-real-time visibility of uncommitted
additions, same-reader-generation translation, directory isolation, and the bitmap copy
before async dispatch.
HnswVectorIndexCreatorTest: 6 tests passed.pinot-segment-local.