Skip to content

feat: support multi-segment indices in hamming clustering#7758

Merged
jackye1995 merged 2 commits into
lance-format:mainfrom
jackye1995:hamming-multi-segment
Jul 13, 2026
Merged

feat: support multi-segment indices in hamming clustering#7758
jackye1995 merged 2 commits into
lance-format:mainfrom
jackye1995:hamming-multi-segment

Conversation

@jackye1995

@jackye1995 jackye1995 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

hamming_clustering_for_ivf_partition and get_ivf_partition_info resolved the target index with find(name) — the first physical segment only. On a logical IVF_FLAT index made of multiple segments (delta segments from append-mode optimize_indices, or distributed builds committed via commit_existing_index_segments), this silently processed only the oldest segment: appended rows were excluded and cross-segment duplicate pairs were never compared, with no error raised.

Both functions now cover all segments of the logical index. Delta segments of one IVF index share centroids, so partition p denotes the same centroid region in every segment; the correct clustering unit is the union of partition p's rows across all segments in a single pairwise pass (representative = min row id, so results are order-independent).

Changes

  • Rust (lance-index::vector::hamming): all-segments behavior for both functions. Every selected segment is validated to share byte-identical global IVF centroids; a mismatch (or missing centroids) raises a descriptive error naming the diverging segment UUIDs and partition counts, advising a retrain. New hamming_clustering_for_ivf_partition_segments / get_ivf_partition_info_segments accept explicit segment UUIDs, following the prewarm_index_segments precedent (feat: support segment selection in pylance prewarm #7677).
  • Python: keyword-only index_segments argument on hamming_clustering_for_ivf_partition and get_ivf_partition_info (None = all segments, the default). Segment-id normalization used in three places (these wrappers, prewarm_index, ScannerBuilder.with_index_segments) is consolidated into lance.util._normalize_index_segment_ids.

Notes

  • Fixes a latent bug: get_ivf_partition_info returned size: 0 for every partition on v3 index files because the loaded IVF model carries no partition lengths. Sizes now come from partition storage via VectorIndex::partition_size.
  • Behavior tightening: get_ivf_partition_info now validates the indexed column is FixedSizeList<UInt8, 8>, a check it previously skipped. This is consistent with the clustering function, which always required 8-byte hashes.
  • An empty explicit segment selection is rejected rather than silently returning "no duplicates", to avoid a silent data-quality hazard in dedup pipelines.

Summary by CodeRabbit

  • New Features

    • Added support for running IVF_FLAT Hamming clustering and partition analysis across multiple index segments.
    • Added optional segment selection to limit clustering and partition statistics to specific physical segments.
    • Segment identifiers now accept UUID strings or UUID objects with validation for invalid selections.
  • Bug Fixes

    • Improved handling and validation of index segment identifiers across dataset and scanner operations.
    • Added validation to ensure selected segments are compatible before processing.

hamming_clustering_for_ivf_partition and get_ivf_partition_info previously
used only the first physical segment of a logical IVF_FLAT index, silently
excluding rows covered by delta segments. They now cover all segments,
validating that every segment shares the same global IVF centroids and
failing otherwise. New _segments variants (Rust) and an index_segments
kwarg (Python) restrict the computation to selected segments, mirroring
prewarm_index. Also fixes get_ivf_partition_info reporting size 0 on v3
index files by reading sizes from partition storage instead of the IVF
model.
Consolidate the three copies of index_segments coercion (hamming clustering
wrappers, prewarm_index, ScannerBuilder.with_index_segments) into
lance.util._normalize_index_segment_ids, and reject a bare str/UUID argument
with a clear TypeError instead of iterating it element-wise.
@github-actions github-actions Bot added A-python Python bindings enhancement New feature or request labels Jul 13, 2026
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

The change adds shared index-segment ID normalization, extends IVF_FLAT hamming clustering and partition-info APIs with optional segment selection, and implements multi-segment processing and validation in Rust with accompanying tests.

Multi-segment IVF_FLAT support

Layer / File(s) Summary
Python segment ID normalization
python/python/lance/util.py, python/python/lance/dataset.py
Centralizes validation and UUID-string conversion for index segment selections and applies it to dataset prewarming and scanner configuration.
Multi-segment clustering engine
rust/lance/src/index/vector/hamming.rs
Opens selected IVF_FLAT segments, validates shared centroids and schemas, combines partition data, clusters hashes, and aggregates partition information.
Python IVF API wiring
python/python/lance/vector.py, python/python/lance/lance/__init__.pyi, python/src/dataset.rs
Adds optional keyword-only index_segments parameters and forwards parsed selections to full-index or segment-scoped Rust operations.
Multi-segment validation coverage
python/python/tests/test_vector.py, rust/lance/src/index/vector/hamming.rs
Tests clustering across multiple fragments, filtering to one segment, shared-centroid validation, and invalid segment selections.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PythonAPI
  participant DatasetBinding
  participant HammingEngine
  participant IVFSegments
  PythonAPI->>DatasetBinding: pass normalized index_segments
  DatasetBinding->>HammingEngine: select full or explicit segment clustering
  HammingEngine->>IVFSegments: open and validate IVF_FLAT segments
  IVFSegments-->>HammingEngine: partition row IDs and hash vectors
  HammingEngine-->>DatasetBinding: return clustering or partition info
  DatasetBinding-->>PythonAPI: return result
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: multi-segment index support for hamming clustering.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rust/lance/src/index/vector/hamming.rs`:
- Around line 227-228: Update the accumulation buffer initialization near
all_row_ids and all_hashes to use Vec::with_capacity(), estimating the combined
capacity by summing partition_size(partition_id) across the opened segments.
Reserve capacity for both vectors before concatenating, while preserving their
existing element types and accumulation behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c8647382-5ba9-4935-a7fb-1dc9d09fd26f

📥 Commits

Reviewing files that changed from the base of the PR and between f0fcb81 and 785a0c7.

📒 Files selected for processing (7)
  • python/python/lance/dataset.py
  • python/python/lance/lance/__init__.pyi
  • python/python/lance/util.py
  • python/python/lance/vector.py
  • python/python/tests/test_vector.py
  • python/src/dataset.rs
  • rust/lance/src/index/vector/hamming.rs

Comment on lines +227 to +228
let mut all_row_ids: Vec<u64> = Vec::new();
let mut all_hashes: Vec<u64> = Vec::new();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Preallocate the accumulation buffers. The combined size is estimable by summing partition_size(partition_id) across the opened segments, so reserving capacity avoids repeated reallocations while concatenating.

♻️ Suggested change
-    let mut all_row_ids: Vec<u64> = Vec::new();
-    let mut all_hashes: Vec<u64> = Vec::new();
+    let estimated_len: usize = segments
+        .iter()
+        .map(|segment| segment.ivf_flat_bin().partition_size(partition_id))
+        .sum();
+    let mut all_row_ids: Vec<u64> = Vec::with_capacity(estimated_len);
+    let mut all_hashes: Vec<u64> = Vec::with_capacity(estimated_len);

As per coding guidelines: "Use Vec::with_capacity() when size is known or estimable, and prefer over-estimating capacity to multiple reallocations."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let mut all_row_ids: Vec<u64> = Vec::new();
let mut all_hashes: Vec<u64> = Vec::new();
let estimated_len: usize = segments
.iter()
.map(|segment| segment.ivf_flat_bin().partition_size(partition_id))
.sum();
let mut all_row_ids: Vec<u64> = Vec::with_capacity(estimated_len);
let mut all_hashes: Vec<u64> = Vec::with_capacity(estimated_len);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance/src/index/vector/hamming.rs` around lines 227 - 228, Update the
accumulation buffer initialization near all_row_ids and all_hashes to use
Vec::with_capacity(), estimating the combined capacity by summing
partition_size(partition_id) across the opened segments. Reserve capacity for
both vectors before concatenating, while preserving their existing element types
and accumulation behavior.

Source: Coding guidelines

@codecov

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.55422% with 38 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rust/lance/src/index/vector/hamming.rs 88.55% 27 Missing and 11 partials ⚠️

📢 Thoughts on this report? Let us know!

@BubbleCal BubbleCal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice changes!

@jackye1995
jackye1995 merged commit ae25040 into lance-format:main Jul 13, 2026
35 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-python Python bindings enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants