Skip to content

[core] Add primary-key vector read kernel#517

Open
JunRuiLee wants to merge 3 commits into
apache:mainfrom
JunRuiLee:feat/pk-vector-position-read
Open

[core] Add primary-key vector read kernel#517
JunRuiLee wants to merge 3 commits into
apache:mainfrom
JunRuiLee:feat/pk-vector-position-read

Conversation

@JunRuiLee

@JunRuiLee JunRuiLee commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

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:

  • materialize selected physical rows from one data file while preserving physical positions and aligned scores;
  • validate and read one-file indexed splits over physical-position ranges;
  • run bucket-local search via the already-merged bucket kernel ([core] Add primary-key vector bucket search kernel #516), merge deterministic global Top-K candidates across buckets, group survivors per data file, and build indexed splits for materialization.

#516 has 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:

  1. pk_vector_position_read.rs — Rust equivalent of Java PrimaryKeyVectorPositionReader.
  2. pk_vector_indexed_split_read.rs — Rust equivalent of Java PrimaryKeyIndexedSplitRead.
  3. pk_vector_orchestrator.rs — synthetic-input orchestration mirroring the read-kernel part of Java PrimaryKeyVectorRead / PrimaryKeyVectorResult.splits().
  4. 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;
  • construction of real ANN and exact-reader dependencies;
  • real vindex ANN segment bytes and end-to-end public-path validation;
  • GlobalIndexSearchMode::Fast routing behavior;
  • refine/rerank from newer Java master.

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.rs

Rust equivalent of Java PrimaryKeyVectorPositionReader.

  • Reads selected 0-based physical positions from a single data file.
  • Appends internal metadata columns:
    • _PKEY_VECTOR_POSITION (Int64, always);
    • _PKEY_VECTOR_SCORE (Float32, only when scores are supplied).
  • Derives returned physical positions from the existing _ROW_ID channel (position = _ROW_ID - first_row_id) instead of guessing from batch offsets.
  • Injects _ROW_ID internally and strips it from final output.
  • Preserves null/deletion-vector physical-position alignment by using the actual rows returned by read_single_file_stream.
  • Rejects reserved metadata-column name conflicts, requested _ROW_ID, missing first_row_id, predicate-bearing readers, empty positions, out-of-range positions, and score-key mismatches.

pk_vector_indexed_split_read.rs

Rust equivalent of Java PrimaryKeyIndexedSplitRead.

  • Adds PkVectorIndexedSplit: one data file + inclusive physical-position ranges + optional scores aligned to expanded position order.
  • Validates single-file input, range bounds, range ordering/non-overlap, non-empty selection, and score length.
  • Expands ranges to physical positions and delegates materialization to PkVectorPositionRead.
  • Rust intentionally fails loud on descending/overlapping physical ranges to protect score alignment. Java normalizes through a bitmap; this is a stricter validation, not a capability regression.

pk_vector_orchestrator.rs

Rust equivalent of the synthetic-input read-kernel part of Java PrimaryKeyVectorRead and PrimaryKeyVectorResult.splits().

  • Calls [core] Add primary-key vector bucket search kernel #516 bucket_search for each synthetic bucket split.
  • Merges candidates with a deterministic 5-level BEST_FIRST key:
    1. distance ascending;
    2. partition bytes unsigned lexicographic;
    3. bucket ascending;
    4. data file name ascending;
    5. row position ascending.
  • Converts final distances to Java-compatible scores via VectorSearchMetric::distance_to_score.
  • Groups survivors by data file into PkVectorIndexedSplits for materialization.
  • Fails loud on cross-split ambiguity, duplicate (file, position), or a hit referencing a file absent from its bucket split.
  • The materialized stream is file/position ordered, matching the indexed-split materialization contract; ranking order is preserved in the candidate list before grouping.

Validation

Synthetic unit/e2e tests cover:

  • position/score alignment using real _ROW_ID values;
  • null rows and deletion-vector interaction;
  • indexed split validation and score alignment;
  • global Top-K tie-breaks across buckets/files/positions;
  • distance-to-score conversion;
  • inactive ANN-source behavior inherited from [core] Add primary-key vector bucket search kernel #516;
  • error paths for malformed inputs.

Validated locally:

  • cargo fmt --all --check
  • cargo clippy -p paimon --lib --tests -- -D warnings
  • cargo test -p paimon pk_vector --lib

Full 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.

@JunRuiLee JunRuiLee force-pushed the feat/pk-vector-position-read branch 2 times, most recently from 048df10 to ff5bb39 Compare July 13, 2026 12:49
@JunRuiLee JunRuiLee changed the title [WIP][core] Add primary-key vector position reader kernel [WIP][core] Add primary-key vector read kernel (position read, indexed split, orchestration) Jul 13, 2026
@JunRuiLee JunRuiLee force-pushed the feat/pk-vector-position-read branch from ff5bb39 to f336b6a Compare July 14, 2026 04:08
@JunRuiLee JunRuiLee changed the title [WIP][core] Add primary-key vector read kernel (position read, indexed split, orchestration) [core] Add primary-key vector read kernel Jul 14, 2026
@JunRuiLee JunRuiLee marked this pull request as ready for review July 14, 2026 06:25
@JunRuiLee JunRuiLee force-pushed the feat/pk-vector-position-read branch from f336b6a to ff5bb39 Compare July 14, 2026 06:27
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)]`.
@JunRuiLee JunRuiLee force-pushed the feat/pk-vector-position-read branch from ff5bb39 to ae7342f Compare July 14, 2026 06:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant