[core] Add primary-key vector read kernel#517
Conversation
048df10 to
ff5bb39
Compare
ff5bb39 to
f336b6a
Compare
ff5bb39 to
ae7342f
Compare
Rust equivalent of Java `PrimaryKeyVectorPositionReader` (reader-kernel subset of apache/paimon#8576). Given one data file, a set of selected 0-based physical positions, and an optional position->score map, it materializes the selected rows and appends internal metadata columns `_PKEY_VECTOR_POSITION` (+ optional `_PKEY_VECTOR_SCORE`). Physical positions are recovered from the `_ROW_ID` column the existing selective read emits (position = _ROW_ID - first_row_id), injected internally and stripped from the output, so a deletion vector dropping rows never desyncs position/score alignment. Reuses the existing single-file read (`read_single_file_stream`, its `_ROW_ID` mechanism, DV intersection) rather than a new read path. Validation (all DataInvalid): empty / negative / out-of-range positions; scores key-set must equal the selected positions; a requested `_ROW_ID` or a reserved metadata column name is rejected; a row-filtering predicate is rejected (`_ROW_ID` + a residual filter desync); a data file without first_row_id. Adds three `pub(super)` `DataFileReader` accessors (read_type, has_row_filtering_predicate, with_read_type) for the sibling module. No crate-level caller yet, so the module and accessors carry a temporary `#[allow(dead_code)]`.
Rust equivalent of Java `PrimaryKeyIndexedSplitRead` (read-path subset of apache/paimon#8576). `PkVectorIndexedSplit` carries one data file + inclusive physical-position ranges + an optional aligned score array; `PkVectorIndexedSplitRead` validates the split, expands the ranges into an ascending position set and a position->score map, and delegates to the sibling position reader. A pure consumer: no bucket/ANN search, no orchestration, no serialization. Validation: exactly one data file; ranges within [0, row_count), strictly ascending and non-overlapping (touching allowed); score length must match the expanded positions. No crate-level caller yet, so it carries a temporary `#[allow(dead_code)]`.
Rust equivalent of Java `PrimaryKeyVectorRead` + the read subset of `PrimaryKeyVectorResult.splits()` (apache/paimon#8579). Per-bucket search via `bucket_search`, cross-bucket global Top-K merge, grouping survivors by data file into `PkVectorIndexedSplit`s, and lazy materialization via `PkVectorIndexedSplitRead`. Global Top-K is a full sort + truncate over the collected candidates (N <= buckets * limit) on a five-level BEST_FIRST key (distance, partition bytes, bucket, file name, row position; `total_cmp` for NaN-safety). Grouping fails loud on cross-split ambiguity, duplicate (file, position), or a hit referencing a file absent from its bucket split. Adds `VectorSearchMetric::distance_to_score` (mirrors Java `PrimaryKeyVectorResult.score(distance)`). Inputs are synthetic per-bucket splits; snapshot/manifest planning, table routing, and real dependency construction are not yet implemented, so the module carries a temporary `#[allow(dead_code)]`.
ae7342f to
53abc01
Compare
|
|
…ering Distance comparators used f32::total_cmp, which orders a negative NaN before every finite value — so a NaN distance (e.g. a non-finite stored vector under inner product, which negates the dot product) would be selected as Top-1. Java Float.compare orders NaN after all finite values. Add a shared java_float_compare helper (NaN last, -0.0 < +0.0) and use it in every comparator that produces or trims Top-K candidates: the bucket-local exact heap and final sort, the ANN result sort, the per-bucket best-first order, and the cross-bucket orchestrator key. Fixing only the orchestrator would be insufficient because the bucket-local Top-K can evict a finite candidate in favor of a NaN before the orchestrator runs. Add signed-NaN unit tests and an exact-search test asserting a NaN candidate never beats a finite Top-1.
|
@JingsongLi good catch. I fixed this with a shared Java-compatible float comparator ( |
Purpose
Linked issue: part of #514 — this PR implements the crate-private PK-vector read kernel / orchestration layer. It does not close the issue.
This is the Rust read-kernel counterpart of Apache Paimon Java apache/paimon#8576 (
PrimaryKeyVectorPositionReader/PrimaryKeyIndexedSplitRead) plus the read-kernel subset of apache/paimon#8579 (PrimaryKeyVectorRead+PrimaryKeyVectorResult.splits()). It is intentionally not the full user-facing PK-vector search integration.Given synthetic per-bucket search inputs, this PR can:
#516has already absorbed the current Java-master behavior for inactive ANN sources (file == null -> continue) and bounded heap merging, so this PR builds on that behavior. The inactive-source handling is therefore intentional and aligned with current Java master, not a fail-loud mismatch.Scope
In scope:
pk_vector_position_read.rs— Rust equivalent of JavaPrimaryKeyVectorPositionReader.pk_vector_indexed_split_read.rs— Rust equivalent of JavaPrimaryKeyIndexedSplitRead.pk_vector_orchestrator.rs— synthetic-input orchestration mirroring the read-kernel part of JavaPrimaryKeyVectorRead/PrimaryKeyVectorResult.splits().VectorSearchMetric::distance_to_score, used when converting final distances into Java-compatible higher-is-better scores.Out of scope for this PR, handled by the follow-up integration PR:
PrimaryKeyVectorScan/ snapshot and index-manifest planning;VectorSearchBuilder/ table routing and public API wiring;GlobalIndexSearchMode::Fastrouting behavior;Because there is no crate-level production caller for these new reader/orchestrator modules yet, they intentionally keep narrow
#[allow(dead_code)]annotations. The follow-up integration PR wires the production caller and removes the allowances for the wired search path.Brief Change Log
pk_vector_position_read.rsRust equivalent of Java
PrimaryKeyVectorPositionReader._PKEY_VECTOR_POSITION(Int64, always);_PKEY_VECTOR_SCORE(Float32, only when scores are supplied)._ROW_IDchannel (position = _ROW_ID - first_row_id) instead of guessing from batch offsets._ROW_IDinternally and strips it from final output.read_single_file_stream._ROW_ID, missingfirst_row_id, predicate-bearing readers, empty positions, out-of-range positions, and score-key mismatches.pk_vector_indexed_split_read.rsRust equivalent of Java
PrimaryKeyIndexedSplitRead.PkVectorIndexedSplit: one data file + inclusive physical-position ranges + optional scores aligned to expanded position order.PkVectorPositionRead.pk_vector_orchestrator.rsRust equivalent of the synthetic-input read-kernel part of Java
PrimaryKeyVectorReadandPrimaryKeyVectorResult.splits().bucket_searchfor each synthetic bucket split.VectorSearchMetric::distance_to_score.PkVectorIndexedSplits for materialization.(file, position), or a hit referencing a file absent from its bucket split.Validation
Synthetic unit/e2e tests cover:
_ROW_IDvalues;Validated locally:
cargo fmt --all --checkcargo clippy -p paimon --lib --tests -- -D warningscargo test -p paimon pk_vector --libFull public-path validation with snapshot/index-manifest planning and real ANN/exact dependencies is intentionally deferred to the follow-up integration PR.
API and Format
No storage-format change. All additions are crate-private (
pub(crate)/pub(super)); no public API is added or changed in this PR.Documentation
No user-facing documentation changes.