Skip to content

feat(encoding): write sparse structural pages explicitly#7755

Closed
Xuanwo wants to merge 1 commit into
xuanwo/sparse-stack-3-sparse-readerfrom
xuanwo/sparse-stack-4-sparse-writer
Closed

feat(encoding): write sparse structural pages explicitly#7755
Xuanwo wants to merge 1 commit into
xuanwo/sparse-stack-3-sparse-readerfrom
xuanwo/sparse-stack-4-sparse-writer

Conversation

@Xuanwo

@Xuanwo Xuanwo commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Part of #7750

Depends on #7754.

Summary

  • Add opt-in Lance 2.3 sparse structural page emission for fields with lance-encoding:structural-encoding=sparse, and reject the request for older file versions.
  • Normalize Arrow structural layers once and share that semantic plan between dense rep/def serialization and sparse position/count serialization.
  • Emit the PR3 sparse wire forms, choose validity polarity by semantic encoded cost, and mini-block compress leaf values.
  • Keep structural-only, all-null, and empty-struct pages on the existing ConstantLayout boundary; explicit non-null constants emit sparse pages.
  • Deliberately leave automatic sparse selection for PR5. Default Lance 2.3 selection and explicit miniblock / fullzip behavior are unchanged.

Coverage

Round trips cover nullable primitive and struct values, list, large-list, map, fixed-size-list, nested combinations, null versus empty lists, both validity polarities, every semantic position/count form, structural-only and all-null pages, page boundaries, scans, ranges, and discontiguous takes. Metadata assertions verify the selected page and set representations. Lance 2.2 default output is compared with explicit mini-block output, and explicit sparse requests are rejected before encoding.

Validation

  • cargo fmt --all -- --check
  • cargo test -p lance-encoding sparse:: --lib (25 passed)
  • cargo test -p lance-encoding (455 passed, 5 ignored)
  • cargo test -p lance-file (92 passed)
  • cargo clippy --all --tests --benches -- -D warnings
  • uv run mkdocs build from docs/ (passed with existing repository link warnings)

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: afdee0c0-643e-40d3-bc7c-49ccbd43c160

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch xuanwo/sparse-stack-4-sparse-writer

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

@github-actions

Copy link
Copy Markdown
Contributor

Important

This PR touches the Lance format specification.

Substantive changes to the format specification — the .proto definitions
and the spec docs under docs/src/format/ — require a PMC vote before merge.
Minor edits such as typo fixes, wording, or formatting are excluded; use your
judgment.

If this is a meaningful format change:

  • Start a vote following the Lance community voting process.
    Format specification modifications need 3 binding +1 votes (excluding the
    proposer), held on GitHub Discussions, with a minimum voting period of 1 week.
  • Once the vote passes, link the completed vote in this PR. It should not be
    merged until the vote is linked.

@github-actions github-actions Bot added A-encoding Encoding, IO, file reader/writer A-format On-disk format: protos and format spec docs enhancement New feature or request labels Jul 13, 2026
@Xuanwo

Xuanwo commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator Author

Already covered by #7754

@Xuanwo Xuanwo closed this Jul 21, 2026
Xuanwo added a commit that referenced this pull request Jul 22, 2026
Part of #7750

## Summary

- In Lance 2.3+, automatically choose sparse structural pages only when
the structural encoding is implicit, the dense mini-block rep/def plan
requires top-level page splitting or has a single row over budget, and
the sparse writer supports the value path.
- Keep within-budget pages on the dense path without constructing a
sparse value block or sparse plan.
- Preserve every explicit structural mode, Lance 2.2 behavior, and dense
fallback/error behavior for dictionary, variable packed-struct, and
other sparse-ineligible value paths.
- Keep the policy decision separate from sparse serialization; this adds
no wire-format fields.

This is the policy/performance layer that turns the already-readable and
explicitly-writable sparse format on automatically only for over-budget
eligible pages.

## Integration

- [PR3 #7754](#7754) absorbed
the readable and explicitly writable sparse format from the closed [PR4
#7755](#7755). Its squash
patch is replayed directly on the current `main` base before the policy
commits in this PR.
- [PR1 #7751](#7751) is
merged; the rebase resolves the stack onto its `MiniBlockRepDefBudget`
naming.


## 100M-row S3 benchmark

Environment: `r7i.8xlarge` in `us-east-2b`, Lance V2.3, 100 files of
1,000,000 rows, 65,536-row write batches, and 1,024-row takes. The final
committed harness is at `66c936391aa46bfcd14f7ac1c820878da68fcc0b` on
`xuanwo/sparse-stack-5-auto-sparse-bench-final`, based on pre-rebase PR5
head `ed0ce68d7f4cceafe6a249c912f7f1da8c4fecd9`. The rebase consolidates
#7754 and resolves #7751 naming without changing the auto-selection or
dense-fallback policy; these measurements remain attributed to that
exact benchmarked head and are not relabeled as current-head results.

Cases:

- `hnsw`: `List<UInt32>`; the first 1/7 of rows (14,285,714 of 100M)
contain 32 consecutive values, followed by an empty tail.
- `uniform`: `List<UInt32>`; one singleton non-empty row every 10,000
rows.
- `deep`: `Struct<events: List<Struct<id: Int32, tags: List<Int32>,
pair: FixedSizeList<Int32; 2>>>>`; non-empty every 4,096 rows with
nullable top struct, event list, event struct, id, tags, pair, and pair
items.

`cold` is the first operation on a newly opened `Dataset`; `warm` is the
immediate repeat on the same `Dataset`. Times are milliseconds. Bytes
are total dataset object bytes. Every dataset has 102 objects, including
100 Lance data files.

| Case | Mode | Bytes | Pages | Cold full scan | Cold random | Warm full
scan | Warm random | Warm empty | Warm non-empty |
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| hnsw | sparse | 1,600,850,504 | 317 | 1,627.830 | 466.940 | 1,472.636
| 272.931 | 0.484 | 29.238 |
| hnsw | mini-block | 1,663,361,256 | 328 | 1,720.362 | 484.619 |
1,655.289 | 273.812 | 0.466 | 20.664 |
| hnsw | full-zip | 2,342,961,549 | 328 | 3,239.070 | 478.839 |
3,104.055 | 273.603 | 0.659 | 25.735 |
| uniform | sparse | 117,091 | 100 | 99.990 | 77.346 | 76.518 | 3.408 |
0.548 | 28.299 |
| uniform | mini-block | 689,601 | 1,600 | 111.844 | 137.503 | 606.852 |
123.301 | 93.404 | 94.822 |
| uniform | full-zip | 496,906,995 | 1,600 | 1,589.645 | 1,205.110 |
650.542 | 359.496 | 72.292 | 58.997 |
| deep | sparse | 1,144,900 | 300 | 159.361 | 77.122 | 135.065 | 9.309 |
0.884 | 25.580 |
| deep | mini-block | 376,199,150 | 10,100 | 6,735.485 | 4,673.904 |
2,560.397 | 582.959 | 174.014 | 189.929 |
| deep | full-zip | 994,111,217 | 10,100 | failed | failed | failed |
failed | failed | failed |

`deep/full-zip` wrote all 100M rows and produced the reported
size/page/object counts, then its first full scan panicked at
`primitive.rs:2588` by unwrapping a missing fixed full-zip decode task.
Sparse and mini-block completed all reads. A separate 200K-row check at
the exact PR4 base SHA `da06b79a7f5eda4f33e2d80990c46766095ba383`
independently confirmed that this nested-null `deep/full-zip` read was
already broken before this PR, returning `Structural validity has 6
entries for an array with 7 values`. The precise failure site varies
with this decoder path, but explicit full-zip never enters PR5's
automatic sparse candidate closure, so this is not an auto-sparse
regression. The policy deliberately does not disguise that behavior as
sparse support.
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 A-format On-disk format: protos and format spec docs enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant