[core] Add primary-key vector bucket search kernel#516
Conversation
The verbatim single-line assert_eq! calls in metric.rs tests exceed rustfmt's fn_call_width and were reflowed to multi-line. No logic change.
The pkvector bucket-search kernel is ported ahead of the read-path wiring (PR3/PR4). Its items are crate-private with no production caller yet, so clippy -D warnings flags them as dead_code. Suppress at the module boundary; remove this allow once PR3/PR4 lands production callers.
Use checked_add for the global deleted-position offset (file_offset + position) to match the surrounding overflow-guarded arithmetic, and correct the scorer-seam doc: negative ANN labels are SKIPPED by the existing vindex reader (collect_results drops row_id < 0), not rejected — so the seam only yields non-negative u64 ordinals and no signed-label handling is needed.
e31520f to
da39ff1
Compare
For example, if a segment covers
|
…bucket Top-K Address both review comments on PR apache#516 (apache/paimon-rust). 1. Inactive ANN sources (correctness/interop with current Java master). Adopt current master PrimaryKeyVectorBucketSearch / PkVectorAnnSegmentSearcher behavior: an ANN segment source that is no longer an active file (e.g. compacted away) is skipped, not rejected, so the bucket still searches the remaining active sources. - build_live_row_ids takes active_source_files and masks out the ordinal ranges of inactive sources (they contribute no live ids; deletion vectors apply only to active sources). Returns None only when all sources are active and no deletion vector is relevant. - map_ann_results defensively rejects a hit resolving to an inactive source (mirrors the Java checkArgument), since the live-row mask should already have excluded it. - bucket_search skips ANN sources missing from the active set; active sources with a mismatched row count are still a hard error. - PkVectorAnnSearcher::search and VindexAnnSearcher thread the active source set through. 2. Bucket Top-K merge complexity. Replace the Vec + max_by linear worst-element scan with a BinaryHeap<WorstFirst> keeping the BEST_FIRST-worst on top, reducing the merge to O(candidate_count log K) and matching the Java PriorityQueue and the exact.rs heap. Final output stays deterministic via a BEST_FIRST sort. Tests: split the old missing-or-mismatched reject into a row-count mismatch reject (still errors) plus skip-inactive coverage; add inactive ordinal-range masking, inactive-hit rejection, and a bounded-heap tie-break eviction test. All 34 pkvector tests pass; fmt and clippy clean.
|
Thanks for the review, both points are addressed in the latest commit. 1. Inactive source files. You're right — this was an interop gap. The PR originally mirrored the read kernel at
I replaced 2. Bucket Top-K complexity. Replaced the I've kept the larger current-master features ( |
Rewrite doc/comments to describe the code itself rather than the porting process, removing references to PR phases, task numbers, upstream commit hashes, and fixture-deferral notes.
Purpose
Linked issue: part of #514 — PR 2 of 5 (does not close the issue)
This is the second, read-only slice of primary-key vector (bucket-local ANN) search support. It mirrors Apache Paimon (Java) apache/paimon#8569 as of commit
a50a36ff8. It builds on the_SOURCE_METAparsing merged in #515.It ports the bucket-local search kernel: given one snapshot bucket's ANN segments, active data files, and deletion vectors, merge ANN hits with an exact fallback over the files not covered by any ANN segment into a deterministic bounded Top-K. This is a pure, synthetically-tested kernel — the read-path wiring and real ANN index-byte validation come in PR 3 / PR 4.
Read-only. Out of scope for this PR (tracked in #514): the physical-position single-file reader (PR 3a), cross-bucket Top-K merge + physical-row materialization (PR 3b), snapshot-consistent planning and end-to-end validation against Java-written fixtures (PR 4), index building, and engine SQL entry points.
Brief change log
New crate-private module
crates/paimon/src/vindex/pkvector/:metric.rs—VectorSearchMetric(l2/cosine/inner_product):normalize/is_supported(lowercase +-→_, no trim, matching Java), parse-once enum,compute_score(higher-better),compute_distance(lower-better, squared-L2 with no sqrt, cosine denominator widened to f64 like Java, zero-norm→0), andscore_to_distance(l21/score-1→infat 0, no clamp).result.rs—PkVectorSearchResult { data_file_name, row_position, distance }.reader.rs—PkVectorReadersequential-scan trait (null rows returnfalsebut still advance the physical position); an in-#[cfg(test)]ArrayReaderfake.exact.rs— bounded-memory exact Top-K over one file (BEST_FIRST = distance ASC, then row_position ASC;total_cmpfor NaN-safety), with dimension/limit/finite/row-count validation and a deletion-vector exclusion predicate.ann.rs—PkVectorAnnSearchertrait (bucket search's ANN dependency, faked in tests) + a structuralVindexAnnSearchercomposed around an injected scorer seam; plus the pure helpersbuild_live_row_ids(live-row mask in segment-ordinal space, offsets guarded bychecked_add) andmap_ann_results(ordinal→(file, position)via [core] Parse primary-key vector index source metadata (_SOURCE_META) #515'sPkVectorSourceMeta::resolve, rejects hits on snapshot-deleted rows,score_to_distance, BEST_FIRST sort).bucket.rs—bucket_search: validates ANN source metadata against active files, skips ANN-covered files in the exact fallback (no rescanning), forwards deletion vectors and search options, and deterministically merges ANN + exact candidates (3-key BEST_FIRST across files).Plus a small cross-module accessor:
DeletionVector::is_deleted(position) -> bool(guards> u32::MAX→ not deleted).The module is crate-private and guarded by a temporary module-level
#![allow(dead_code)]: PR 2 ports the kernel ahead of the read-path wiring, so it has no production caller yet. The allow is removed once PR 3 / PR 4 lands callers.Tests
All synthetic unit tests inside the module (30 tests; full
cargo test -p paimongreen;cargo fmt --all --checkandcargo clippy -p paimon --all-targets -- -D warningsboth clean):PkVectorExactSearcherTest), normalize no-trim, cosine zero-norm→0, cosine f64-sqrt precision (pinned with an irrational-norm case), score_to_distance l2→inf.build_live_row_idsNone-when-no-relevant-DV + multi-file-offset masking;map_ann_resultsordinal→position mapping + score, out-of-range ordinal rejection, ANN-hit-on-deleted rejection; adapter composition through the scorer seam.is_deletedmembership +> u32::MAXguard.Real ANN vector-search correctness (driving the
vindexreader with genuine index bytes) is intentionally not exercised here — thevindexcrate is read-only, so index bytes cannot be synthesized in-process. It is deferred to PR 4's Java-written fixtures.API and Format
_SOURCE_METAformat defined by Java [core] Add primary-key vector index foundation paimon#8549 (parsed in [core] Parse primary-key vector index source metadata (_SOURCE_META) #515) and reproduces the search semantics of [core] Add primary-key vector bucket search paimon#8569.pub(crate)); the one cross-module accessorDeletionVector::is_deletedispub, matching that type's existing public accessors (cardinality,is_empty, …). No existing API changed.Documentation
No user-facing documentation changes.