Skip to content

[Vector Upsert 4/5] Apply the query's visible-document set before vector candidate generation - #19300

Open
xiangfu0 wants to merge 4 commits into
apache:masterfrom
xiangfu0:xiangfu0/upsert-vector-4-upsert-candidate-scope
Open

[Vector Upsert 4/5] Apply the query's visible-document set before vector candidate generation#19300
xiangfu0 wants to merge 4 commits into
apache:masterfrom
xiangfu0:xiangfu0/upsert-vector-4-upsert-candidate-scope

Conversation

@xiangfu0

@xiangfu0 xiangfu0 commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Part 4/5 of the split of #19287 (Fix FULL-upsert vector candidate generation).
This is the core fix. Stacked on #19299.

Summary

FilterPlanNode constructed and executed VECTOR_SIMILARITY before adding
SegmentContext.getDocIdsSnapshot() as an outer AND. Obsolete physical versions of
upserted rows could therefore occupy per-segment ANN top-K slots and be removed only
afterward, producing fewer than K rows or omitting nearer current rows.

Pass the snapshot to vector predicates as a required VectorCandidateScope so it
constrains candidate generation, while retaining the outer bitmap AND as defense in
depth. FilterPlanNode remains the only place that knows the scope comes from the
segment's queryable-document snapshot; the operators only ever see "documents this
predicate may consider". That snapshot is already more general than upsert — it is the
queryable-document set, which also covers delete tombstones and skipUpsertDelete — so
no upsert vocabulary leaks into pinot-core's filter operators.

The execution path is chosen at plan time, where both the reader capability and
forward index availability are known:

  • a filter-aware reader receives the scope and performs filtered ANN;
  • a reader that cannot restrict its search is bypassed in favour of
    ExactVectorScanFilterOperator over the allowed documents, reported through
    fallbackReason;
  • an empty scope needs no candidate generation and no reader capability at all, so it
    short-circuits to an empty operator;
  • when neither path is available the query fails clearly.

Because the choice is made once, VectorSimilarityFilterOperator needs no runtime
exact-scan branch, and it captures the reader capability once instead of re-reading it
per execution, so a plan built on one answer can never execute against another. Required
scopes stay separate from optimizer-selected metadata filters and are intersected with
them before candidate generation. Non-upsert queries keep the adaptive metadata
behavior, non-vector plans do not copy the snapshot, and the new explain attributes are
emitted only for queries that actually carry a scope.

Performance and compatibility

ANN approximation semantics are unchanged. Segments whose reader cannot restrict its
search use a correctness-first exact scan whose cost is proportional to the
allowed-document count times vector dimension; consuming segments of upsert tables take
this path today because MutableVectorIndex is not filter-aware. Making the mutable
index filter-aware would retire that fallback and can be done separately.

Validation

  • Core vector/filter suite: 101 tests passed
    (FilterPlanNodeTest, VectorSimilarityFilterOperatorTest,
    FilterAwareVectorSearchTest, ExactVectorScanFilterOperatorTest,
    VectorRadiusFilterOperatorTest, VectorSearchStrategyTest).
  • Spotless, Checkstyle, and license checks passed.

Stack

  1. [Vector Upsert 1/5] Support allowed-document filtering in exact vector scan #19297 — exact-scan allowed-document filtering
  2. [Vector Upsert 2/5] Harden vector search metric recording and backend param cleanup #19298 — backend param cleanup hardening
  3. [Vector Upsert 3/5] Never materialize nested vector subtrees as metadata filters #19299 — nested vector metadata guard
  4. [Vector Upsert 4/5] Apply the query's visible-document set before vector candidate generation #19300 — apply the visible-document set before candidate generation (core fix)
  5. [Vector Upsert 5/5] Add FULL-upsert vector integration coverage #19301 — integration coverage

@codecov-commenter

codecov-commenter commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.95028% with 20 lines in your changes missing coverage. Please review.
✅ Project coverage is 57.88%. Comparing base (f5fee8e) to head (064abd0).

Files with missing lines Patch % Lines
...operator/filter/ExactVectorScanFilterOperator.java 86.44% 5 Missing and 3 partials ⚠️
...perator/filter/VectorSimilarityFilterOperator.java 91.89% 1 Missing and 5 partials ⚠️
...ava/org/apache/pinot/core/plan/FilterPlanNode.java 87.50% 2 Missing and 3 partials ⚠️
...not/core/operator/filter/VectorCandidateScope.java 87.50% 1 Missing ⚠️

❗ There is a different number of reports uploaded between BASE (f5fee8e) and HEAD (064abd0). Click for more details.

HEAD has 2 uploads less than BASE
Flag BASE (f5fee8e) HEAD (064abd0)
unittests 2 1
unittests2 1 0
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #19300      +/-   ##
============================================
- Coverage     67.12%   57.88%   -9.24%     
+ Complexity     1424        7    -1417     
============================================
  Files          3462     2663     -799     
  Lines        220677   160702   -59975     
  Branches      35255    26548    -8707     
============================================
- Hits         148136    93028   -55108     
+ Misses        60708    59760     -948     
+ Partials      11833     7914    -3919     
Flag Coverage Δ
integration 100.00% <ø> (ø)
integration1 100.00% <ø> (ø)
integration2 0.00% <ø> (?)
java-25 57.88% <88.95%> (-9.24%) ⬇️
lane-a 100.00% <ø> (ø)
lane-b 0.00% <ø> (ø)
temurin 57.88% <88.95%> (-9.24%) ⬇️
unittests 57.88% <88.95%> (-9.24%) ⬇️
unittests1 57.88% <88.95%> (+0.07%) ⬆️
unittests2 ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@xiangfu0
xiangfu0 force-pushed the xiangfu0/upsert-vector-4-upsert-candidate-scope branch from ca0c7f0 to 7ee68b3 Compare August 19, 2026 01:38
@xiangfu0 xiangfu0 changed the title [Vector Upsert 4/5] Apply the FULL-upsert snapshot before vector candidate generation [Vector Upsert 4/5] Apply the query's visible-document set before vector candidate generation Aug 19, 2026
@xiangfu0 xiangfu0 added bug Something is not working as expected vector Related to vector similarity search query Related to query processing upsert Related to upsert functionality data-integrity Related to correctness of data or query results needs-attention Used for sensitive changes - allows searching PRs post release to narrow down causes for regression. labels Aug 19, 2026
…r scan

Extract a shared computeExactMatches helper that unifies the exact top-K and
threshold scan paths and can restrict scoring to an allowed-document bitmap.

Introduce VectorCandidateScope, an immutable set of document IDs a vector
predicate is allowed to consider as candidates. Vector top-K is not monotonic,
so unlike an ordinary predicate -- which is correct to intersect with the result
afterwards -- a document set that defines what the query may see has to be
applied before candidate generation. The type carries that contract and says
nothing about where the restriction came from; callers decide that.
ExactVectorScanFilterOperator accepts one via a new constructor overload, and no
production caller passes one yet.

Also let the operator record its own exact-scan search and fallback metrics,
which were previously invisible to VectorSearchMetrics, and report the applied
candidate-filter cardinality in explain output.

Add filtered-reader coverage for HnswVectorIndexReader.
… param cleanup

Only record a vector search metric when a search actually executed, and
guarantee backend search parameters are cleared even when configuration fails
partway or the explain-context refresh throws. Previously a failure inside
configureBackendParams still recorded a search that never ran, and an exception
from refreshExplainContext could skip clearBackendParams entirely.
…ata filters

FilterPlanNode treated any non-leaf AND sibling as a metadata filter when
wiring pre-filter bitmaps for filter-aware ANN. A nested subtree containing a
VECTOR_SIMILARITY predicate (e.g. AND(vector, AND(vector2, metadata)) or
AND(vector, NOT(vector2))) was eagerly materialized into a bitmap, executing
top-K candidate generation out of its boolean context and pushing its results
into the sibling vector search as a pre-filter.

Track the retained child FilterContexts alongside their operators so
wirePreFilterForVectorOperators can pair them, and only treat subtrees with no
vector predicate as metadata. Adaptive pre-filter selection for plain metadata
siblings is unchanged.
…tor candidate generation

FilterPlanNode constructed and executed VECTOR_SIMILARITY before adding
SegmentContext.getDocIdsSnapshot() as an outer AND. Obsolete physical versions
of upserted rows could therefore occupy per-segment ANN top-K slots and be
removed only afterward, producing fewer than K rows or omitting nearer current
rows.

Pass the snapshot to vector predicates as a required VectorCandidateScope so it
constrains candidate generation, while retaining the outer bitmap AND as defense
in depth. FilterPlanNode remains the only place that knows the scope comes from
the segment's queryable-document snapshot; the operators only see 'documents
this predicate may consider'.

Choose the execution path at plan time, where both the reader capability and
forward index availability are known:

- a filter-aware reader receives the scope and does filtered ANN;
- a reader that cannot restrict its search is bypassed for
  ExactVectorScanFilterOperator over the allowed documents, which reports it
  through fallbackReason;
- an empty scope needs no candidate generation and no reader capability at all,
  so it short-circuits to an empty operator;
- when neither path is available the query fails clearly.

Because the choice is made once, VectorSimilarityFilterOperator no longer needs
a runtime exact-scan branch, and it captures the reader capability once instead
of re-reading it per execution, so a plan built on one answer can never execute
against another. Required scopes stay separate from optimizer-selected metadata
filters and are intersected with them before candidate generation. Non-upsert
queries keep the adaptive metadata behavior, and non-vector plans do not copy
the snapshot.
@xiangfu0
xiangfu0 force-pushed the xiangfu0/upsert-vector-4-upsert-candidate-scope branch from 7ee68b3 to 064abd0 Compare August 20, 2026 09:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something is not working as expected data-integrity Related to correctness of data or query results needs-attention Used for sensitive changes - allows searching PRs post release to narrow down causes for regression. query Related to query processing upsert Related to upsert functionality vector Related to vector similarity search

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants