Repository navigation
feat(format)!: add file metadata size hint - #9168
Ali2Arslan wants to merge 4 commits into
Conversation
Co-authored-by: Cursor <cursoragent@cursor.com>
|
Important Format specification voteThis PR modifies the Lance format specification, so it requires 3 binding +1 votes from PMC members (excluding the proposer), at least one of them on the latest commit, and a minimum 72-hour voting period, weekends excluded, before it can merge. Vote by approving this PR (+1) or requesting changes (−1, a veto). See the voting process. Approvals carry over across pushes, so a rebase or a typo fix does not send everyone back to re-vote. Whoever approves the latest commit is vouching that nothing substantive has changed since the earlier approvals; if something has, ask for fresh votes. Status: ❌ Blocked — 1 of 3 required approvals
Updated automatically by the format-spec vote gate, which re-checks every 15 minutes — just voted? Re-check now (press Run workflow; leave the input blank to re-check every open format PR). A PMC member may apply the |
Co-authored-by: Cursor <cursoragent@cursor.com>
…ze-hint-spec DataFile now lives in fragment_metadata.proto, so the metadata size hint is added there. Co-authored-by: Cursor <cursoragent@cursor.com>
| * Lance use it as key of the base_paths field in Manifest to determine the actual base path of the data file. | ||
| */ | ||
| optional uint32 base_id = 7; | ||
|
|
There was a problem hiding this comment.
issue (blocking): "Schema descriptor" isn't a defined term in the file spec, and this is the durable contract. Acceptance: the proto comment and docs state exactly which byte offset the suffix starts at (derived from footer fields for v2.x) and whether v1/legacy files get a value or stay 0, so independent writers produce identical values.
|
|
||
| ### File Metadata Size Hints | ||
|
|
||
| `DataFile.file_metadata_size_bytes` records the suffix length from the Lance |
There was a problem hiding this comment.
issue (blocking): Same definition gap: "suffix length from the Lance schema descriptor" needs a precise anchor (offset in file-format terms). Also reconcile "Lance-format" here with "current-format" in the PR body.
|
|
||
| </details> | ||
|
|
||
| ### File Metadata Size Hints |
There was a problem hiding this comment.
nitpick: This section lands between the DataFile message block and the "Field-to-column mapping" note, which belongs to the Data Files section. Consider placing it after that note.
| file_minor_version: proto.file_minor_version, | ||
| file_size_bytes: CachedFileSize::new(proto.file_size_bytes), | ||
| base_id: proto.base_id, | ||
| file_metadata_size_bytes: NonZero::new(proto.file_metadata_size_bytes), |
There was a problem hiding this comment.
issue (blocking): The interner path (intern_fragment) is what manifest loads use, and nothing tests that it carries the hint. Acceptance: a test builds a pb::DataFragment with a non-zero file_metadata_size_bytes, runs it through intern_fragment, and asserts the value survives (and that 0 gives None).
| .map(|f| IndexFile { | ||
| path: f.path.clone(), | ||
| size_bytes: f.size_bytes, | ||
| file_metadata_size_bytes: NonZero::new(f.file_metadata_size_bytes), |
There was a problem hiding this comment.
issue (blocking): The RewrittenIndex ↔ proto conversion (this line and the From impl below) changed with no test. Acceptance: a roundtrip test with a non-zero hint on a rewritten index's files.
| fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> { | ||
| use serde::ser::SerializeStruct; | ||
| let mut s = serializer.serialize_struct("DataFile", 7)?; | ||
| let field_count = 7 + usize::from(self.file_metadata_size_bytes.is_some()); |
There was a problem hiding this comment.
question (non-blocking): Why omit the field from serde output when None rather than always emitting it? Non-self-describing formats (bincode-style) would then mismatch the deserialize helper. Is DataFile serialized that way anywhere? Also, please add a JSON test for both present and absent cases.
Anchor the hint at global buffer 0, which holds the FileDescriptor, in both proto comments and the table format docs. Legacy (0.1) files and non-Lance index files always record 0, and writers record either the exact value or 0. Move the docs section after the field-to-column note. Always serialize DataFile.file_metadata_size_bytes, like file_size_bytes and base_id. Python's DataFile now accepts and carries the value because FragmentMetadata.from_json builds it from Rust-serialized fragment JSON. Cover the hint in the manifest interner, the RewrittenIndex conversion, and JSON serialization. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
✅ Gate recommendation: approve.
2aaa5f4e addresses the suffix-definition concern: the proto comments and spec now identify the exact FileDescriptor boundary and when the value must be zero. JSON and Python carry the optional value consistently, with regression coverage for absence and round trips.
The contract remains advisory, with footer-authoritative reads and no change to the file grammar. This format contract can land before #9169, which supplies the reader/writer optimization.
wjones127
left a comment
There was a problem hiding this comment.
This looks good to me. Thanks for adding the clarification. 😄
Proposal
Add one advisory
file_metadata_size_bytesvalue toDataFileandIndexFile. For a Lance file of format version 2.0 or later, it is the file size minus the position of global buffer 0, which holds the file'sFileDescriptor. That position is stored in the first entry of the global buffer offset table, which the footer locates. The value is always zero for legacy (0.1) files and for index files in other formats; zero also means unknown. Writers record either this exact value or zero.Footer offsets and counts remain authoritative. Readers ignore a value larger than the file and read any metadata the footer references outside the hinted range.
Rationale
A full metadata open always needs this complete suffix. Recording its exact size lets a reader schedule the required bytes immediately instead of waiting for a footer read before scheduling the metadata prefix. This optimizes dependency depth independently of physical request count; the scheduler may still split a large logical range into concurrent requests.
Projected data-file opens do not need another persisted value: an implementation can estimate the CMO table from the manifest's existing physical-column mappings and retain a prefix-only fallback.
Compatibility
Option<NonZeroU64>, so the hint occupies oneu64slot.DataFileJSON always includesfile_metadata_size_bytes(nullwhen unknown), likefile_size_bytesandbase_id. Python'sDataFileaccepts and carries the value becauseFragmentMetadata.from_jsonbuilds it from that JSON, for example for write progress and distributed compaction.IndexFileconversion initialize the new fields as unavailable until the follow-up implementation propagates them.Per the format-change process, this PR contains only the durable contract, documentation, and minimum compilation/round-trip updates. The reader/writer implementation is prepared in #9169 and remains draft until this change merges.
Maintainer action
Gatekeeper requested the
breaking-changelabel. GitHub does not permit fork contributors to apply repository labels, so a maintainer must add it.Validation
cargo test -p lance-table --lib -- test_to_json test_intern_fragment_file_metadata_size_bytes test_rewritten_index_roundtrips test_roundtrip_fragment test_index_metadata_codec_roundtripuv run pytest python/tests/test_fragment.py python/tests/test_optimize.py python/tests/test_dataset.py::test_dataset_progresscargo clippy --all --tests --benches -- -D warningsuv run make lint(frompython/)python3 ci/check_proto_comments.py protos/table.proto protos/fragment_metadata.protoMade with Cursor