Proposal: Planar Page Layout for Lance 2.3 #7635
Xuanwo
started this conversation in
Lance File Format
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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
Lance 2.3 replaces the one-message-per-layout shape of
PageLayoutwith a single planar pair: a structural plane (how nested structure is represented: dense rep/def levels, or sparse slot mappings) times a value plane (how values are stored: chunked, zipped, constant, or none). MiniBlock, FullZip, Constant, AllNull, and the landed Sparse layout (#7628) all become cells of this matrix; "miniblock" and "fullzip" stop being format concepts and survive as user-facing aliases for the dense × chunked and dense × zipped combinations — the semantics of today'sMiniBlockLayoutandFullZipLayout. Every combination that 2.3 keeps is bit-for-bit identical to its 2.2 physical format (one deliberate exception: complex constant/all-null pages switch to the sparse representation, which is smaller), so this is a metadata-level rework, not a new on-disk data format.This proposal introduces no new encoding and leaves the performance of existing pages untouched — the sparse gains ship with #7628, not here. What the planar model buys is composability and evolution: combinations that each would have required another hand-written layout become plain cells of the matrix — sparse × zipped for mostly-null wide-value columns (e.g. backfilled embedding columns, which no layout serves today), sparse × constant/none replacing the page-level rep/def buffers of complex constant/all-null pages, dictionary alongside sparse structure — and any future structural or value representation (RLE/bitmap structure, new value encodings) is a oneof variant in one plane instead of a new layout re-implementing the other plane.
Problem
In 2.1/2.2, every structural idea hardens into a
PageLayoutoneof variant that re-implements its own surroundings. Dense rep/def — a single structural representation — already exists in three placement copies:MiniBlockLayout(levels interleaved inside chunks),FullZipLayout(per-record control words), andConstantLayout(page-level buffers, used by complex all-null pages). The landedSparseLayout(#7628) continues the pattern: it duplicates miniblock's value fields yet lacks dictionary support, adds a fourth parallel scheduler silo on the read side, and welds sparse structure to chunked value storage, so it cannot serve sparse wide-value columns. Meanwhile one kind of decision — how values are stored — is split across two format levels (the miniblock/fullzip choice and constant detection). On this trajectory, every structural-representation × value-storage combination costs a hand-written layout.Proposed Design
PageLayout is the planar pair
The division of labor: the structural plane maps rows to visible items and rebuilds repdef; the value plane stores exactly the
num_visible_itemsvisible leaf values and maps visible items to bytes. Everything structurally invisible — nulls, empty lists — is the structural plane's job.A new
protos/encodings_v2_3.proto(packagelance.encodings23, following the 2.0→2.1 precedent) imports allCompressiveEncodingmessages fromencodings21unchanged. Placement is not an independent axis — where structural bytes live is fully determined by the (structural codec, value codec) pair — so there is no top-level layout variant left to choose.BlobLayoutdoes not carry over. Blob v2 (since 2.2) stores descriptor structs through regular layouts plus an object layer and never emitsBlobLayoutpages; v1BlobLayoutremains legacy-read-only. Should a genuinely non-planar layout ever appear, it gets a new proto package rather than a speculative extension point here.The two planes
Why
DenseRepDefcarries no physical fields: sparse structural metadata is index-like — O(sparsity) small, stored in its own page buffers, read once at page open, after which all addressing happens in memory. Dense levels are payload-like — O(items) large — so they must ride the value storage at its own IO granularity (interleaved in chunks, or as per-record control words), which is what preserves single-IO cold random access on nested data and is exactly where 2.1 already stores them.DenseRepDef.layersalone determines which level streams exist: a list layer implies a rep stream (even all-valid lists have variable-length offsets), a nullable layer implies a def stream, and the repetition index accompanies rep. Each value codec'sDenseLevelssub-message describes only how its storage carries the declared streams; readers validate the two by derivation, the same waynum_positionsmust match set cardinality.New cells unlocked by the matrix (every dense × * cell maps to an existing 2.2 form; see coverage below):
SparseLayout(#7628), re-mounted; bytes unchangedrows + 1tovisible + 1entries (4 orders of magnitude at 99.99% null, expected); takes read exactly one value; empty takes do zero IOTwo legality rules keep every page's encoding canonical:
ALL_VALID_*layers, zero structural bytes), so a separate variant would only create a second wire encoding for the same page. Trivial structure must always be written as the dense degenerate form, never as sparse. The bar for a structural codec is a distinct cost/capability profile; dense and sparse clear it, all-valid does not.ConstantLayoutdoes, or ORC-style full stream separation more generally — was considered and rejected: it either costs an extra IO per nested random access or a new chunk byte format. Worst case for the sparse replacement, a constant page with dense random nulls, is ~2–3x larger structural metadata — small in absolute terms, and a future bitmap structural codec can close it.) With value codecnone, the structural plane must make every leaf invisible.Coverage of existing 2.1/2.2 forms
pb2.0 (Lance 2.0 files) is out of scope: 2.0 has no page-layout concept — structure lives in the
ArrayEncodingtree — and it already runs on a frozen legacy path in the reader. Every pb21 form (2.1/2.2 files, plus the 2.3-unstable interimSparseLayout) maps into the planar model with physical bytes unchanged:MiniBlockLayoutrepetition_index_depth,has_large_chunk)FullZipLayoutbits_per_value(fixed width)bits_per_offset(variable width; second buffer is therows + 1row-offset index)bits_rep = bits_def = 0(NIL control words)ConstantLayout(formerlyAllNullLayoutin 2.1; evolved in place by #5641)inline_valuepresent, no rep/def buffersinline_valuepresent + rep/def buffersinline_valueabsent (all-null, incl. compressed rep/def since #4990)BlobLayout(v1 blob)inner_layoutmapped recursivelySparseLayout(#7628, 2.3-unstable interim)Compatibility and Migration
structural_encodingfield-metadata values"miniblock"/"fullzip"are kept and map to forcing the historical combinations dense × chunked / dense × zipped (which also suppresses automatic sparse selection, matching the PR feat: add sparse structural encoding #7628 behavior where an explicit request disables auto-sparse).Binary/LargeBinaryblob fields still take the v1 encoder without a version gate; a 2.3 writer returns an explicit error pointing to blob v2 struct input, with no silent conversion.SparseLayoutlands first within unstable 2.3; this rework must complete before 2.3 stabilizes, so that the stable format freezes the planar model rather than four layout variants. Files written with the interimSparseLayoutcarry no compatibility promise — they remain readable through the mapping above, or can be regenerated.SparseLayout→ sparse × chunked) into one internal representation, with the writer switching to the new metadata; acceptance is byte-for-byte equivalence (dense combinations against 2.2, sparse × chunked againstSparseLayoutpages), and the only behavior change is complex constant/all-null pages switching to sparse × constant/none, whose encode path already exists; (2) sparse × zipped writer support for mostly-null wide-value columns.Beta Was this translation helpful? Give feedback.
All reactions