Skip to content

perf(compaction): skip capturing row addresses when no index to remap#7773

Open
zhangyue19921010 wants to merge 5 commits into
lance-format:mainfrom
zhangyue19921010:perf/skip-rowid-capture-when-no-index
Open

perf(compaction): skip capturing row addresses when no index to remap#7773
zhangyue19921010 wants to merge 5 commits into
lance-format:mainfrom
zhangyue19921010:perf/skip-rowid-capture-when-no-index

Conversation

@zhangyue19921010

@zhangyue19921010 zhangyue19921010 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

What

Compaction on datasets with address-style row ids always added the _rowid column to the scan and collected every rewritten row address into an O(rows) RoaringTreemap, then serialized it back in the RewriteResult — even when there were no indices to remap. That work is pure waste in the no-index case.

Change

Capture row addresses only when needed like remap action.

Impact

For no-index legacy compactions this removes an extra _rowid scan column, an O(rows) in-memory treemap, and its serialization/transfer to the coordinator. Behavior is unchanged for indexed, defer_index_remap, and stable-row-id datasets. Concurrent index creation stays safe.

Summary by CodeRabbit

  • Bug Fixes

    • Refined compaction so row-address metadata is captured only when the dataset needs it for index remapping.
    • Tightened when index remapping runs, ensuring it only triggers if the required row-address data was produced.
    • Improved behavior for datasets without non-system indexes, especially when stable row IDs are in use.
  • Tests

    • Added regression coverage to verify row-address capture is skipped when no remapping-relevant indexes exist.
    • Updated the deferred-remap test setup to include the missing awaited index creation.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c8b0cbc4-4d39-4700-9ba1-a0ae9c938332

📥 Commits

Reviewing files that changed from the base of the PR and between c6271aa and ef4e970.

📒 Files selected for processing (1)
  • rust/lance/src/dataset/optimize.rs

📝 Walkthrough

Walkthrough

Compaction now gates row-address capture on applicable index-remapping paths and requires captured address data before commit-time remapping. Tests cover datasets without non-system indices and align deferred-remap comparison setup.

Changes

Compaction row-address remapping

Layer / File(s) Summary
Gate row-address capture
rust/lance/src/dataset/optimize.rs
Compaction checks for non-system indices and passes a refined capture flag to reader preparation and rewrite handling; a regression test verifies capture is skipped without such indices.
Require captured mappings for commit remapping
rust/lance/src/dataset/optimize.rs
Commit-time remapping requires completed tasks with captured row addresses, and the deferred-remap test creates the comparison index on both dataset handles.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: jackye1995, wjones127, xuanwo

🚥 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 accurately summarizes the main change: compaction now skips row-address capture when no remappable index exists.
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

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/dataset/optimize.rs`:
- Around line 2998-3040: The test test_skip_rowid_capture_when_no_index should
use named rstest cases via #[case::...], replacing #[values(false, true)] so
failures identify the indexed and unindexed scenarios. Strengthen the result
assertions by verifying row_addrs is absent without an index and present with an
index containing a non-empty serialized mapping, rather than checking only
is_some().
🪄 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: 855d2ac3-ab4f-4861-b5bd-fa42617a7701

📥 Commits

Reviewing files that changed from the base of the PR and between bc2d137 and d3fdc24.

📒 Files selected for processing (1)
  • rust/lance/src/dataset/optimize.rs

Comment thread rust/lance/src/dataset/optimize.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rust/lance/src/dataset/optimize.rs (1)

3370-3379: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse create_scalar_index for dataset2.

This block duplicates the helper introduced at Lines 2339-2350. Calling create_scalar_index(&mut dataset2, "i", false).await keeps the two comparison datasets consistent and avoids future drift.

As per coding guidelines: extract logic repeated in two or more places into a shared helper.

🤖 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/dataset/optimize.rs` around lines 3370 - 3379, Replace the
inline dataset2.create_index call with the existing create_scalar_index helper,
passing &mut dataset2, "i", and false, then await its result. Keep the
comparison dataset setup otherwise unchanged.

Source: Coding guidelines

🤖 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/dataset/optimize.rs`:
- Around line 3768-3779: Extend the FRI verification in the test around
recorded_old to also iterate over indexed and assert that every indexed fragment
is present in recorded_old, using the same all-or-nothing assertion behavior as
the existing unindexed_frags check.
- Around line 1466-1471: Update the fragment range construction in the dataset
optimization flow to replace the unchecked max_fragment_id increment with
checked_add(1). Propagate a contextual error when the maximum fragment ID cannot
be incremented, while preserving the empty-range behavior for None and the
inclusive-to-exclusive range semantics for valid IDs.

---

Outside diff comments:
In `@rust/lance/src/dataset/optimize.rs`:
- Around line 3370-3379: Replace the inline dataset2.create_index call with the
existing create_scalar_index helper, passing &mut dataset2, "i", and false, then
await its result. Keep the comparison dataset setup otherwise unchanged.
🪄 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: be579c85-d5b2-436b-8b1d-261c7317f97a

📥 Commits

Reviewing files that changed from the base of the PR and between d3fdc24 and c6271aa.

📒 Files selected for processing (1)
  • rust/lance/src/dataset/optimize.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rust/lance/src/dataset/optimize.rs (1)

3370-3379: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse create_scalar_index for dataset2.

This block duplicates the helper introduced at Lines 2339-2350. Calling create_scalar_index(&mut dataset2, "i", false).await keeps the two comparison datasets consistent and avoids future drift.

As per coding guidelines: extract logic repeated in two or more places into a shared helper.

🤖 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/dataset/optimize.rs` around lines 3370 - 3379, Replace the
inline dataset2.create_index call with the existing create_scalar_index helper,
passing &mut dataset2, "i", and false, then await its result. Keep the
comparison dataset setup otherwise unchanged.

Source: Coding guidelines

🤖 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/dataset/optimize.rs`:
- Around line 3768-3779: Extend the FRI verification in the test around
recorded_old to also iterate over indexed and assert that every indexed fragment
is present in recorded_old, using the same all-or-nothing assertion behavior as
the existing unindexed_frags check.
- Around line 1466-1471: Update the fragment range construction in the dataset
optimization flow to replace the unchecked max_fragment_id increment with
checked_add(1). Propagate a contextual error when the maximum fragment ID cannot
be incremented, while preserving the empty-range behavior for None and the
inclusive-to-exclusive range semantics for valid IDs.

---

Outside diff comments:
In `@rust/lance/src/dataset/optimize.rs`:
- Around line 3370-3379: Replace the inline dataset2.create_index call with the
existing create_scalar_index helper, passing &mut dataset2, "i", and false, then
await its result. Keep the comparison dataset setup otherwise unchanged.
🪄 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: be579c85-d5b2-436b-8b1d-261c7317f97a

📥 Commits

Reviewing files that changed from the base of the PR and between d3fdc24 and c6271aa.

📒 Files selected for processing (1)
  • rust/lance/src/dataset/optimize.rs
🛑 Comments failed to post (2)
rust/lance/src/dataset/optimize.rs (2)

1466-1471: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '1435,1495p' rust/lance/src/dataset/optimize.rs
printf '\n--- SEARCH max_fragment_id ---\n'
rg -n "max_fragment_id" rust/lance -S

Repository: lance-format/lance

Length of output: 8578


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'optimize.rs slice\n'
sed -n '1458,1476p' rust/lance/src/dataset/optimize.rs

printf '\nmanifest definition candidates\n'
rg -n "max_fragment_id|struct Manifest|enum Manifest" rust/lance -S

Repository: lance-format/lance

Length of output: 7081


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'manifest field/type definitions\n'
rg -n "max_fragment_id" rust/lance/src/dataset.rs rust/lance/src/dataset/transaction.rs rust/lance/src -S

printf '\n--- dataset.rs around manifest definition ---\n'
sed -n '1,220p' rust/lance/src/dataset.rs

printf '\n--- transaction.rs around relevant update logic ---\n'
sed -n '2620,2665p' rust/lance/src/dataset/transaction.rs

Repository: lance-format/lance

Length of output: 16604


Guard the fragment upper bound conversion. m + 1 can overflow at the maximum fragment id and drop the last fragment from the bitmap; use checked_add(1) and return a contextual error instead.

🤖 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/dataset/optimize.rs` around lines 1466 - 1471, Update the
fragment range construction in the dataset optimization flow to replace the
unchecked max_fragment_id increment with checked_add(1). Propagate a contextual
error when the maximum fragment ID cannot be incremented, while preserving the
empty-range behavior for None and the inclusive-to-exclusive range semantics for
valid IDs.

Source: Coding guidelines


3768-3779: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that indexed fragments are retained too.

The test only verifies that unindexed_frags appear in the FRI. It would still pass if the indexed group were accidentally omitted, so also assert that every fragment in indexed is recorded.

Suggested assertion
+        for fragment_id in indexed {
+            assert!(
+                recorded_old.contains(&(fragment_id as u64)),
+                "indexed fragment {fragment_id} must be recorded in the FRI"
+            );
+        }
📝 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.

        let recorded_old: HashSet<u64> = details
            .versions
            .iter()
            .flat_map(|v| v.old_frag_ids())
            .collect();
        for f in &unindexed_frags {
            assert!(
                recorded_old.contains(f),
                "unindexed fragment {f} must be recorded in the FRI (all-or-nothing)"
            );
        }
        for fragment_id in indexed {
            assert!(
                recorded_old.contains(&(fragment_id as u64)),
                "indexed fragment {fragment_id} must be recorded in the FRI"
            );
        }
    }
🤖 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/dataset/optimize.rs` around lines 3768 - 3779, Extend the FRI
verification in the test around recorded_old to also iterate over indexed and
assert that every indexed fragment is present in recorded_old, using the same
all-or-nothing assertion behavior as the existing unindexed_frags check.

@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.66667% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rust/lance/src/dataset/optimize.rs 91.66% 0 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant