feat(format): promote data overlay files to GA with an explicit enable setting - #8016
Draft
wjones127 wants to merge 2 commits into
Draft
feat(format): promote data overlay files to GA with an explicit enable setting#8016wjones127 wants to merge 2 commits into
wjones127 wants to merge 2 commits into
Conversation
Data overlay files were gated behind a build-profile check plus the `LANCE_ENABLE_UNSTABLE_DATA_OVERLAY_FILES` escape hatch: release builds treated feature flag 64 as unknown and refused any dataset carrying an overlay. Remove the gate so release builds read and write overlays, and rename `FLAG_UNSTABLE_DATA_OVERLAY_FILES` to `FLAG_DATA_OVERLAY_FILES`. `apply_feature_flags` already derives the flag from the manifest's fragments on every commit, so the flag is set by the commit that attaches the first overlay and cleared by the commit that removes the last one. Add tests pinning that lifecycle end to end, covering both ways overlays disappear: deleting the overlaid fragments, and compacting them into base data. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
|
Important This PR touches the Lance format specification. Substantive changes to the format specification — the If this is a meaningful format change:
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Data overlay files were permitted on any dataset, which leaves nowhere to record that a table wants overlays before its first one exists. That is what a high-level writer (update, merge_insert) needs to consult when deciding whether to store an update as an overlay. Add `lance.overlays.enabled`, a table config key resolved as: an explicit boolean wins; otherwise a dataset already carrying overlays counts as enabled (so datasets written before the key existed stay writable); otherwise the library default, currently off. Enablement lives in config rather than in feature flag 64 so it survives writers that predate overlays, and so an enabled-but-overlay-free dataset stays open to pre-overlay readers. The flag keeps its existing meaning: overlays are present. Disabling is a two-way door, allowed only while no fragment carries an overlay. Both that check and the converse -- no overlay commit while disabled -- are one invariant enforced in `build_manifest`, which re-runs on conflict rebase, so a disable that races a concurrent `DataOverlay` is rejected rather than silently committed. Surfaces: `WriteParams::enable_overlays` (an `Option<bool>`, since the default is expected to flip and a bare bool could not distinguish "off" from "unspecified"), `Dataset::set_overlays_enabled`, and `Dataset::remove_overlays`, which compacts overlaid fragments and nothing else via a new `CompactionOptions::overlays_only`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Two commits, best reviewed in order.
1. Remove the unstable gate
Data overlay files were gated behind a build-profile check plus an escape hatch: release builds treated feature flag 64 as unknown and refused any dataset carrying an overlay, unless
LANCE_ENABLE_UNSTABLE_DATA_OVERLAY_FILESwas set.data_overlay_files_enabled()/supported_flags_when()and the env var.can_read_dataset/can_write_datasetcheck a plainSUPPORTED_FLAGSconstant again.FLAG_UNSTABLE_DATA_OVERLAY_FILEStoFLAG_DATA_OVERLAY_FILES(value unchanged at 64).The flag lifecycle you asked about already worked.
apply_feature_flagsrecomputes the flag from the manifest's fragments on every commit, so it is set by the commit that attaches the first overlay and cleared by the commit that removes the last one. No production change was needed; it was just untested, so this pins it: a unit test for set-then-clear, plus 4 e2e tests covering aDataOverlaycommit setting it, deleting the overlaid fragments clearing it, compaction clearing it, and it staying set while any other fragment is overlaid. The clearing cases reopen the dataset so they assert against the persisted manifest.2. Gate overlay writes on an explicit setting
Overlays were permitted on any dataset, so there was nowhere to record that a table wants overlays before its first one exists — which is what a high-level writer (
update,merge_insert) needs to consult when deciding whether to store an update as an overlay.Where enablement lives
lance.overlays.enabled, a table config key — not feature flag 64:Manifest::new_from_previousclonesconfigverbatim, so a config key round-trips through writers that have never heard of it.reader_feature_flagsis a hard refusal gate. An enabled-but-overlay-free dataset is byte-for-byte readable by a pre-overlay reader, and setting bit 64 on it would exclude them for no correctness reason. This matters more once the default flips: enablement-as-flag would set bit 64 on every newly created table.Flag 64 keeps its existing meaning — overlays are present.
Resolution, and the coming default flip
true/falsewins.DEFAULT_OVERLAYS_ENABLED, currentlyfalse.The plan is to flip step 3 to
truein ~6 months so existing tables get the optimization — a one-line change. Two consequences are baked in deliberately:WriteParams::enable_overlaysisOption<bool>, notbool.rust/CLAUDE.mdsays to name booleans sofalse/Default::default()is the desired default, which makesenable_*: boolwrong once the default is on, and renaming a public field todisable_*later is a break.falseexplicitly rather than relying on absence, since absence is the thing designed to change.Not in this PR, and I'd treat it as a prerequisite for the flip: a session-level default, so a deployment can opt out fleet-wide before upgrading rather than table-by-table after. Resolution order would become explicit config → session default → library default;
overlays_enabled_withis the single place it slots into.The disable guard
One invariant — "disabled implies no overlays" — covers both directions: disabling while overlays remain, and committing a
DataOverlaywhile disabled. It lives inbuild_manifestrather than at the API boundary becausebuild_manifestre-runs on conflict rebase.check_update_config_txnputsUpdateConfigvsDataOverlayin the compatible bucket, so a disable racing an overlay write gets rebased and replayed against the newer manifest, and the check fires.test_disable_racing_a_concurrent_overlay_is_rejectedcovers that path.One subtlety worth a look: enablement resolves against the pre-commit fragment list. Resolving against post-commit fragments would let a
DataOverlayauthorize itself, since the overlays it is adding would satisfy clause 2.API
WriteParams::enable_overlays: Option<bool>— honored on Create and Overwrite; append leaves the setting alone.Dataset::overlays_enabled()/Dataset::set_overlays_enabled(bool).Dataset::remove_overlays()— compacts overlaid fragments and nothing else. This needed a newCompactionOptions::overlays_only:max_overlays_per_fragment: Some(0)alone still bins small neighbors together and materializes deletions, and the obvious workaround of zeroingtarget_rows_per_fragmentis unsafe because it doubles asmax_rows_per_filefor the rewrite.Notes for review
FLAG_UNSTABLE_DATA_OVERLAY_FILESandENABLE_UNSTABLE_DATA_OVERLAY_FILES_ENVwerepubinlance-tableand are removed rather than deprecated. Keeping "UNSTABLE" in the name after GA seemed worse than the break, and no released build could actually use the feature. Happy to add deprecated aliases.FileWriter::write_columnadvances one field at a time, so they are), and "Scheduling compaction" claimed compaction was unimplemented (max_overlays_per_fragmentexists). Corrected both.Testing
cargo fmt --allandcargo clippy --all --tests --benches -- -D warningsclean. Fullcargo test -p lance --libgreen (2619) andcargo test -p lance-table --libgreen (135).cargo checkclean for the python and java crates. Also ran the lance-table flag tests under--releaseto confirm the unstable gate is gone withdebug_assertionsoff — they would have failed before.🤖 Generated with Claude Code