Repository navigation
fix: start a new data file before a dictionary outgrows its key type - #10368
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
✅ Gate recommendation: approve.
The fixed-size digest tracker addresses the retained-memory finding. File splitting preserves the declared dictionary key types and released-reader compatibility. No blocking issues remain.
Xuanwo
approved these changes
Oct 30, 2026
# Conflicts: # rust/lance/src/dataset/versions/mod.rs # rust/lance/src/dataset/write.rs
…closes - MemWAL generations opt out of dictionary-driven splits; their deletion vector and indexes address a single fragment. - After a dictionary-driven close, take no more rows than the current file has room for, so max_rows_per_file still holds. - Count only the items within a sliced list.
Contributor
Author
|
Thank you @Xuanwo |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #10367.
A narrow-key dictionary column (
Int8,UInt8,Int16,UInt16keys) could not be written or compacted once the batches that go into one data file referenced more distinct values than the key type addresses, even though each batch fit. The 2.1+ encoder merges the dictionaries of the batches it buffers into one page and, since #9958, rejects the page withInvalidInput.The fragment writer now tracks, per data file, the distinct values each such column holds at any struct or list depth (
DictionaryValueBudget). It finishes the file before a batch would push a column past its key capacity and continues in a new one, through the same close path asmax_bytes_per_file. When compaction has planned row counts, the remaining rows are rebalanced the same way they are after a byte-limit close. Each data file is decoded on its own, so readers are unchanged. Files written this way read back with pylance 13.0.0 and 2.0.0. A file closed this way still ends atmax_rows_per_filerows, since the writer takes no more rows than the current file has room for.Values are counted with
encoded_dictionary_valuesin lance-encoding, which follows the encoder's null handling. The encoder drops key validity, so the in-range key under a null slot also selects a value. Only value types the encoder can merge exactly are tracked: strings, binary, integers and floats. Columns of other value types keep the encoder's error.Not covered, all keeping today's behavior:
Map.Compaction plans by row count, so it rewrites the split fragments into the same number of fragments on every run. Before this change it failed on every run.
Tests
write_splits_files_before_dictionary_outgrows_key_type: three batches of 100 values starting at 0, 28 and 128, written in one go and through compaction, on 2.1 and 2.2, with and without stable row IDs. It checks that the first file holds exactly 128 values ([2000, 1000]rows), that compaction removes 3 fragments and adds 2, and that the values read back.write_keeps_max_rows_per_file_after_dictionary_close: three 150-row batches withmax_rows_per_file = 200, where the first file closes early for its dictionary. It checks that the files hold[150, 200, 100]rows, so the next file still stops at the row limit.test_flush_rejects_dictionary_overflow_instead_of_splitting: a MemWAL flush whose Int8 dictionary column holds 200 distinct values fails with the encoder's key-type error instead of writing a second fragment.dictionary_budgetunit tests: top-level, struct-child and list-item dictionaries, a sliced list, themax_rowscap, a value reached only through a null slot, an unreferenced first value, empty batches, view-typed values, and untracked value types.test_encoded_dictionary_values: in-range and out-of-range keys under null slots, and null values.