Skip to content

perf(encoding): compact mini-block chunk index for cached page state#7651

Open
Ali2Arslan wants to merge 4 commits into
lance-format:mainfrom
Ali2Arslan:perf/miniblock-chunk-index
Open

perf(encoding): compact mini-block chunk index for cached page state#7651
Ali2Arslan wants to merge 4 commits into
lance-format:mainfrom
Ali2Arslan:perf/miniblock-chunk-index

Conversation

@Ali2Arslan

Copy link
Copy Markdown
Contributor

Summary

The mini-block structural scheduler caches per-page state that is consulted on
every read. Today that state is a Vec<ChunkMeta> (num_values, size, offset per
chunk) plus a repetition index of per-chunk blocks (first_row,
starts_including_trailer, has_preamble, has_trailer) — ~48 bytes/chunk, almost
all of it derivable from a handful of cumulative quantities.

This PR replaces both with a compact MiniBlockChunkIndex (new
primitive/chunk_index.rs submodule) that stores only the non-redundant data
and derives the rest:

  • byte_starts: PrefixSums — cumulative chunk byte sizes, stored as u32
    when the page's data buffer fits in 4 GiB and u64 otherwise.
  • rows: RowMapping selected by page shape:
    • UniformFlat — fixed-width / bitpacked pages where every non-last chunk
      holds the same number of values; row↔chunk is pure arithmetic with no
      heap allocation
      .
    • Flat — non-uniform flat pages (RLE / FSST); a single cumulative value
      array.
    • Nested — repetition present; cumulative row starts + a has_trailer
      bitmap (preamble derived from the previous chunk's trailer) + leaf item
      counts.

byte_range/items_in_chunk/find_chunk/first_row/rows_in_chunk/
has_preamble/has_trailer preserve the exact semantics the scheduler relied
on (find_chunk still returns the first of duplicated start rows). Row→chunk
lookups remain O(1) (uniform flat) or O(log n) (binary search), and the
multi-range instruction merge in schedule_instructions now runs in place
instead of allocating a second Vec.

MiniBlockChunkIndex implements DeepSizeOf (composing the inner
PrefixSums/ItemCounts/RowMapping), so cache weighing reflects the smaller
footprint.

This is an idiomatic port of an internal change to current upstream: it uses
lance's DeepSizeOf trait (rather than a bespoke size method), threads the
existing initialize IO structure, and drops fork-only test scaffolding that
has no counterpart here.

Test plan

  • cargo test -p lance-encoding --lib — 422 passed, 0 failed, 5 ignored
    (covers flat, non-uniform-flat, and nested/list miniblock round trips,
    e.g. test_sparse_large_string_list::...MINIBLOCK,
    test_sparse_boolean_list_uses_miniblock, test_nested_strings).
  • New unit tests: page-shape detection (test_flat_detection cases),
    nested row/item axes (test_nested_detection_and_axes), uniform-vs-Flat
    scheduler/lookup parity (test_uniform_flat_matches_prefix_sum_flat),
    per-variant heap-size bounds vs the legacy layout
    (test_deep_size_per_variant_below_legacy), and a parse_nested_rep
    oracle test against the previous per-block decode.
  • cargo fmt -p lance-encoding
  • cargo clippy -p lance-encoding --tests --benches -- -D warnings

Made with Cursor

The mini-block scheduler cached each page as a `Vec<ChunkMeta>` plus a
repetition index of per-chunk blocks (~48 bytes/chunk), most of which is
derivable from a few cumulative quantities.

Replace both with a compact `MiniBlockChunkIndex` that stores only the
non-redundant data and derives the rest: prefix-sum byte offsets (narrowed
to `u32` when the page fits) and a row mapping chosen per page shape --
`UniformFlat` (arithmetic, no allocation) for fixed-width/bitpacked pages,
`Flat` for non-uniform flat pages (RLE/FSST), and `Nested` (row prefix sums
+ a trailer bitmap) when a repetition index is present. Row->chunk lookups
stay O(1)/O(log n) and the scheduler's multi-range merge now runs in place.

The `DeepSizeOf` impl accounts for every retained buffer so cache weighing
reflects the smaller footprint. Adds unit tests for page-shape detection,
byte-range/row/item axes, uniform-vs-flat scheduler parity, and per-variant
size bounds.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions github-actions Bot added A-encoding Encoding, IO, file reader/writer performance labels Jul 7, 2026
Trim the docs and comments added in the previous commit to the project
convention: explain why not what, drop pure narration, and keep each to
2-3 lines. Comment/doc only; no behavior change.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.13740% with 15 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ing/src/encodings/logical/primitive/chunk_index.rs 95.83% 10 Missing and 2 partials ⚠️
.../lance-encoding/src/encodings/logical/primitive.rs 98.72% 2 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

impl DeepSizeOf for MiniBlockCacheableState {
fn deep_size_of_children(&self, context: &mut Context) -> usize {
self.rep_index.deep_size_of_children(context)
self.chunk_index.deep_size_of_children(context)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this also fixes the under-reporting (wasn't accounting for chunk_meta before)

// are _adjacent_ (i.e. don't merge "take first row of chunk 0" and "take third row of chunk 0" into "take 2
// rows of chunk 0 starting at 0")
if user_ranges.len() > 1 {
// TODO: Could probably optimize this allocation away

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

bit of a drive-by, but addressed this as well

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.

1 participant