Proposal: Native Sparse Structural Layout for Lance 2.3 #7631
Replies: 1 comment 1 reply
-
|
My overall thought is that it makes a lot of sense we would want a new structural layout for sparse data. I'm not entirely certain I understand the proposal yet. I think the basic argument here is to store a "selection list" instead of a "selection bitmap" (repdef) which would be a good approach I think.
I agree this is a big problem. I had hoped to handle it by changing the page planner to create pages based on the compressed size. Though this is a bit tricky because it means we have to worry about:
How does this proposal address this problem? Do you keep accumulating after you've picked a sparse plan? Or maybe "page" here means mini-block chunk?
Good point.
In this table the "stored sparse state" for a validity layer is "null positions". If the data is sparse wouldn't you want that to be "valid positions"? This is somewhat in contrast with the list layer because in the list layer both valid AND null are rare (with empty list being the common case) if I am understanding correctly. Actually, is empty list the common case? Or is null list the common case? Or do you need to plan for both cases? |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Summary
This proposal adds
SparseLayout, a new structural page layout in Lance file format 2.3 for sparse nested data. Instead of expanding Arrow structure into a dense repetition/definition event stream, a sparse page stores each structural layer as a native slot-domain mapping — non-empty list slots, their child counts, and null slots — while the value side stores only the leaf values that actually exist, still mini-block compressed. Arrow semantics (null list vs. empty list, struct validity, fixed-size-list dimensions) are preserved exactly, and structural cost becomesO(non_empty_slots + null_slots + count_variation)instead ofO(rows)orO(structural_events).The target workloads are mostly-empty list columns, sparse nested structs, and ML metadata where leaf values are far fewer than rows. On a 100M-row benchmark over S3, sparse layout reduces a combined table from 137 MB / 9,933 pages (mini-block) to 105 MB / 6 pages, and cuts warm random-take latency on a deeply nested sparse column from ~1.6 s to ~1 ms.
Problem
Today Lance expresses nested structure as a dense rep/def stream carried by the mini-block or full-zip layouts. This works well for dense nested data, but sparse nested data pays a linear cost for its default states. A typical sparse shape is: most list slots are valid empty lists, a few slots have child ranges, a few slots are null, and leaf values are far fewer than top-level rows.
Compressing the rep/def stream (e.g. RLE) only fixes the bytes. Two costs remain structural:
Proposed Design
Structural layers
A sparse page describes Arrow structure as ordered layers, outer-most to inner-most (
SparseStructuralLayer):Absence has defined semantics: a list slot in neither
non_empty_positionsnornull_positionsis a valid empty list; a validity slot not innull_positionsis valid; a fixed-size-list slot maps to a child range determined by the dimension. Each layer records its parent and child domain sizes (num_slots,num_child_slots) so readers can validate layer chaining.Semantic position and count sets
Structural sets are semantic, not necessarily materialized arrays:
SparsePositionSetis one of empty, all, range (one contiguous slot range), or explicit (a delta-compressed, strictly increasingu64buffer).SparseCountSetis one of empty, constant (one child count shared by all non-empty list slots), or explicit (a compressedu64buffer).Common shapes therefore need no structural payload at all: an all-valid layer is
null_positions = empty; a dense prefix of non-empty lists is arange; a fixed-degree adjacency column (e.g. HNSW neighbor lists) isrange/explicit + constant.Page contents
Page metadata (
SparseLayout) stores the value compression descriptor, the structural layers, the total structural item count (num_items), and the visible leaf value count (num_visible_items). Values reuse the existing mini-block value compression path unchanged; only the structural side is new.Validation
Readers must reject malformed sparse pages with a format error rather than decode garbage: the physical buffer list must match the layer metadata exactly (one buffer per explicit set, no unused compression descriptors), explicit positions must be strictly increasing and within the layer domain, per-layer domain sizes must chain, and chunk metadata must sum to
num_visible_itemsand to the value buffer size.Read Semantics
The reader decodes the structural layers once into a cacheable structural plan, then translates selected top-level ranges layer by layer into child ranges:
If the projected leaf ranges are empty, the reader reconstructs offsets and validity purely from the plan and reads no value payload. This read contract — never expanding a dense structural stream on the hot path — matters more than any internal decoder structure; implementations may use any adapter that preserves Arrow semantics.
Layout Selection
Sparse is a page layout, not a column-level promise; readers identify it only via
PageLayout.sparse_layout. Writers select it two ways:lance-encoding:structural-encoding=sparse(invalid before 2.3; the metadata is a writer input only and must never be used by readers to infer page layout).The automatic policy is deliberately conservative and is not part of the on-disk contract — it can evolve toward a real cost model without any format change. Mini-block and full-zip remain the layouts for dense nested data and for value types the sparse writer does not yet cover.
Compatibility and Migration
SparseLayoutis gated on file version 2.3, which is unstable. Version 2.2 stable files are unchanged, and writers must not emit the layout for older versions. Readers that predate the newPageLayoutvariant reject 2.3 files under the existing version-compatibility rules, so no old reader ever misreads a sparse page.The reference implementation currently has these limitations, all compatible with the contract above: dictionary-encoded and packed-struct value blocks are not yet sparse-encoded (they fall back to dense layouts); structural sets are stored as separate page buffers rather than one packed structural sidecar; and automatic selection is intentionally narrower than what the format allows.
Beta Was this translation helpful? Give feedback.
All reactions