Skip to content

perf: support indexed metadata for structural projections#7790

Merged
Xuanwo merged 3 commits into
mainfrom
xuanwo/indexed-metadata-structural-projection
Jul 15, 2026
Merged

perf: support indexed metadata for structural projections#7790
Xuanwo merged 3 commits into
mainfrom
xuanwo/indexed-metadata-structural-projection

Conversation

@Xuanwo

@Xuanwo Xuanwo commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

Indexed metadata loading currently rejects V2.1+ projections with explicit ordinary structural children. As a result, selecting a struct, list, or nested child from an ultrawide file still reads and deserializes metadata for every physical column even when the data-page projection is narrow.

ReaderProjection already records the selected physical leaves in DFS order, and indexed loading already compacts the selected metadata and renumbers decoder indices to 0..N. The remaining blocker was the conservative eligibility check, which accepted only childless 1:1 logical-to-physical projections.

This PR replaces that check with validation against the number of physical leaves required by the projected structural tree. It enables indexed metadata for ordinary full, partial, nullable, and reordered structural projections when the required physical-leaf count matches the selected column indices.

Blob and packed-struct fields remain unsupported by indexed metadata and continue to use the existing full-metadata reader. This PR does not add packed-struct projection or reconstruction logic.

Test coverage includes multi-leaf structs, partial and reordered projections, nullable structural fields, lists, metadata-subset I/O, and fixed-size-list regression cases.

Performance

The unrelated quadratic schema reconstruction cost was removed by #7766 before running these benchmarks.

The benchmarks used Criterion on an m7i.4xlarge, with a single Lance file containing 65,536 physical columns. The measured path is the ordinary structural path retained in this PR.

Backend Projection Eager median Indexed median Speedup Latency reduction
Local, cold cache Full two-leaf struct 166.305 ms 83.942 ms 1.98x 49.5%
Local, cold cache One struct child 168.935 ms 85.201 ms 1.98x 49.6%
S3 Full two-leaf struct 352.042 ms 250.338 ms 1.41x 28.9%
S3 One struct child 350.283 ms 241.908 ms 1.45x 30.9%

The metadata I/O tradeoff is:

Backend Projection Eager bytes / reads Indexed bytes / reads Read reduction Read-count change
Local Full two-leaf struct 39,867,058 / 7 32,220,134 / 9 7,646,924 bytes (19.2%) +2
Local One struct child 39,866,920 / 5 32,219,879 / 7 7,647,041 bytes (19.2%) +2
S3 Full two-leaf struct 39,867,058 / 7 32,281,574 / 9 7,585,484 bytes (19.0%) +2
S3 One struct child 39,866,920 / 5 32,281,319 / 7 7,585,601 bytes (19.0%) +2

Indexed loading still reads the complete column-metadata offset table and then issues reads for the selected metadata ranges, so the two additional logical reads are expected.

These benchmarks isolate the single-file reader path. They do not measure manifest/schema planning or sidecar-file fan-out. Scalar and primitive fixed-size-list projections were already eligible and serve only as regression controls, not as part of the newly enabled structural projection scope.

@github-actions github-actions Bot added A-encoding Encoding, IO, file reader/writer performance labels Jul 14, 2026
@Xuanwo Xuanwo marked this pull request as ready for review July 15, 2026 08:06
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Indexed metadata loading now supports structural projections by counting their required physical columns while excluding blob and packed-struct fields. Tests cover nested reads, physical-column remapping, metadata IO, opaque projections, and fragment heuristics.

Changes

Indexed projection handling

Layer / File(s) Summary
Physical-column counting and validation
rust/lance-file/src/reader.rs
Indexed projection support traverses structural projection shapes, matches physical-column counts, and updates unsupported-projection messaging.
Structural projection read coverage
rust/lance-file/src/reader.rs
New generators and assertions test fixed-size-list and nested projections, eager/lazy result parity, metadata IO bounds, and physical-column remapping.
Opaque projection and heuristic tests
rust/lance-file/src/reader.rs, rust/lance/src/dataset/fragment.rs
Parameterized tests cover blob and packed-struct metadata rejection, and fragment tests verify selected physical-column heuristic behavior.

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

Sequence Diagram(s)

sequenceDiagram
  participant ProjectedFileReader
  participant FileMetadataProvider
  participant ProjectionShape
  ProjectedFileReader->>FileMetadataProvider: Validate indexed projection
  FileMetadataProvider->>ProjectionShape: Count structural physical columns
  ProjectionShape-->>FileMetadataProvider: Return count or opaque result
  FileMetadataProvider-->>ProjectedFileReader: Allow indexed loading or return NotSupported
Loading

Suggested labels: A-index

Suggested reviewers: westonpace, wjones127

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding indexed metadata support for structural projections.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch xuanwo/indexed-metadata-structural-projection

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.

Caution

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

⚠️ Outside diff range comments (1)
rust/lance-file/src/reader.rs (1)

3436-3474: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Preamble None-projection check is unrelated to metadata_key and now runs redundantly per case.

The block that opens with None and asserts InvalidInput (zero-column projection) doesn't depend on metadata_key at all, yet with the new #[case::blob(...)] / #[case::packed_struct(...)] parameterization it now executes identically twice. Consider hoisting it into its own standalone test so the parameterized test only covers the opaque-projection-rejection behavior it's named for.

♻️ Suggested split
-    async fn test_lazy_reader_rejects_opaque_projection(#[case] metadata_key: &str) {
+    async fn test_lazy_reader_rejects_opaque_projection(#[case] metadata_key: &str) {
         let fs = FsFixture::default();
         let written_file = create_some_file(&fs, LanceFileVersion::V2_1).await;
 
         let ordinary_projection = ReaderProjection::from_column_names(
             LanceFileVersion::V2_1,
             &written_file.schema,
             &["location.x"],
         )
         .unwrap();
         assert_eq!(ordinary_projection.schema.fields[0].children.len(), 1);
         assert!(ProjectedFileReader::supports_projection(
             &ordinary_projection,
             LanceFileVersion::V2_1
         ));
 
         let file_scheduler = fs
             .scheduler
             .open_file(&fs.tmp_path, &CachedFileSize::unknown())
             .await
             .unwrap();
-        let err = ProjectedFileReader::try_open(
-            file_scheduler.clone(),
-            None,
-            Arc::<DecoderPlugins>::default(),
-            &test_cache(),
-            FileReaderOptions::default(),
-        )
-        .await
-        .unwrap_err();
-        assert!(
-            matches!(err, lance_core::Error::InvalidInput { .. }),
-            "expected InvalidInput, got {err:?}"
-        );
-
         let mut projection = ordinary_projection;

(Move the removed block into a new, unparameterized #[tokio::test] async fn test_lazy_reader_rejects_empty_projection().)

🤖 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-file/src/reader.rs` around lines 3436 - 3474, Extract the
`ProjectedFileReader::try_open` block using `None` and the `InvalidInput`
assertion from `test_lazy_reader_rejects_opaque_projection` into a standalone
`#[tokio::test]` named `test_lazy_reader_rejects_empty_projection`. Keep
`test_lazy_reader_rejects_opaque_projection` parameterized so it only exercises
behavior dependent on `metadata_key`.
🤖 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.

Outside diff comments:
In `@rust/lance-file/src/reader.rs`:
- Around line 3436-3474: Extract the `ProjectedFileReader::try_open` block using
`None` and the `InvalidInput` assertion from
`test_lazy_reader_rejects_opaque_projection` into a standalone `#[tokio::test]`
named `test_lazy_reader_rejects_empty_projection`. Keep
`test_lazy_reader_rejects_opaque_projection` parameterized so it only exercises
behavior dependent on `metadata_key`.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a8031457-e671-4b4f-8d79-c620aef8812a

📥 Commits

Reviewing files that changed from the base of the PR and between 6f6601f and b53ce91.

📒 Files selected for processing (2)
  • rust/lance-file/src/reader.rs
  • rust/lance/src/dataset/fragment.rs

@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Xuanwo Xuanwo merged commit 31a1e50 into main Jul 15, 2026
33 checks passed
@Xuanwo Xuanwo deleted the xuanwo/indexed-metadata-structural-projection branch July 15, 2026 09:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-encoding Encoding, IO, file reader/writer performance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants