Skip to content

feat(reader): resolve unknown delete-file sizes lazily - #3345

Open
unikdahal wants to merge 1 commit into
apache:mainfrom
unikdahal:reader/lazy-delete-file-size
Open

unikdahal wants to merge 1 commit into
apache:mainfrom
unikdahal:reader/lazy-delete-file-size

Conversation

@unikdahal

Copy link
Copy Markdown

Which issue does this PR close?

What changes are included in this PR?

Execution engines that build FileScanTasks themselves do not always carry a delete file's size, and a delete file with file_size_in_bytes == 0 cannot currently be opened.

  • Tasks planned from manifest entries carry the recorded size and are unaffected. Externally constructed tasks may now use 0 when the size is unavailable (documented on FileScanTaskDeleteFile::file_size_in_bytes).
  • BasicDeleteFileLoader resolves a zero size with one metadata request when the Parquet delete file is first loaded. CachingDeleteFileLoader already loads each delete file once per reader, so this happens once per delete file even when several data files reference it.
  • A failed metadata request is reported as Failed to stat delete file '…'; BasicDeleteFileLoader and CachingDeleteFileLoader::load_deletes keep the storage error's kind and retryability.
  • A size below the 8-byte Parquet footer minimum, whether resolved from storage or supplied by the task, fails the read with DataInvalid rather than dropping the file's deletes.
  • Known sizes take the existing path with no metadata request. Deletion vectors do not use this size and are unchanged.

No public type or method signatures change; FileScanTaskDeleteFile::file_size_in_bytes additionally accepts 0 for externally constructed tasks where the size is unavailable.

Not in this PR

  • ArrowReader with a data-file concurrency of 1 wraps task errors as Unexpected; that pre-existing wrapping is left to a separate change.
  • If loading a delete file fails (metadata or open), its delete-cache entry stays Loading: later positional-delete waiters for the same file are never notified, and the spawned equality-delete task panics on the dropped channel. This is pre-existing for open failures with known sizes; a failed metadata request is one more way to reach it. Fixing it needs failure propagation to waiters (they must not proceed as if there were no deletes), so it is left to a separate change.

Are these changes tested?

Yes:

  • A metadata-counting test storage shows known sizes issue no metadata request and unknown sizes issue exactly one per delete file, including when another task references an already loaded delete file; both produce identical deleted positions.
  • Unknown-size equality deletes loaded through CachingDeleteFileLoader for two data files issue one metadata request and produce a bound predicate equal to the known-size one; parsing an unknown-size equality delete file yields an equal predicate.
  • An encrypted positional delete file is read with both its known size and an unknown size.
  • A missing delete file with an unknown size reports Failed to stat delete file with exactly the storage error's kind and retryability, from the basic loader and through CachingDeleteFileLoader::load_deletes; a retryable storage failure stays retryable.
  • A zero-byte object is rejected for both an unknown and a too-small supplied size.

AI Disclosure

This change was developed with assistance from Claude Code and reviewed by me. Tests and CI results come from this branch.

Execution engines that build scan tasks themselves do not always carry a
delete file's size. Tasks planned from manifest entries keep the
recorded size; externally constructed tasks may now set
FileScanTaskDeleteFile::file_size_in_bytes to 0 when the size is
unavailable, and the Parquet delete-file loader resolves it with one
metadata request when the file is first loaded. The caching loader
loads each delete file once per reader, so the request is made once per
delete file even when several data files reference it.

A failed metadata request is reported with the delete-file path while
keeping the storage error's kind and retryability. A size below the
Parquet footer minimum, whether resolved or supplied by the task, fails
the read rather than dropping the file's deletes. Deletion vectors do
not use this size and are unchanged.
Copilot AI balanced review requested due to automatic review settings October 4, 2026 21:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@JeonDaehong JeonDaehong left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heads-up @unikdahal
#3145 (approved) makes the FileScanTaskDeleteFile fields private and routes both the builder and deserialization through validate(). Merging it with this PR causes conflicts in delete_file_loader.rs and scan/task.rs, and the two tests that set delete.file_size_in_bytes = 0 will no longer compile once the field is private.

Since #3145 is already modifying this type for 0.11, would it make sense to model the unknown size as an Option<u64> (with the getter returning Option<u64>) instead of using 0 as a sentinel? That would make "unknown" explicit for external callers, and validate() could still check the known case.

I checked that the deletion vector path doesn't use file_size_in_bytes, so the DV note in your description holds.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support unknown delete-file sizes in externally constructed scan tasks

3 participants