Core: Read DELETE manifests in ReachableFileCleanup - #17745
Conversation
ManifestFiles.readPaths previously required a DATA manifest and threw IllegalArgumentException on DELETE manifests. ReachableFileCleanup calls readPaths on every manifest reachable only through expired snapshots; when one was a DELETE manifest (as happens after a branch that added delete files is removed, or any expire with withIncrementalCleanup(false)), the exception was logged and swallowed by the suppressFailureWhenFinished task loop, silently orphaning the referenced delete and DV files on object storage. Fix readPaths to dispatch through ManifestFiles.open so it works for both DATA and DELETE manifests. This centralizes the fix (readPaths has no other callers) and keeps ReachableFileCleanup unchanged. Add a regression test in TestRemoveSnapshots that exercises the branch removal path where a delete manifest is uniquely owned by an expired snapshot.
c20825a to
557a788
Compare
|
thanks @dkranchii for the contribution, but I think the new test To be clear, the delete manifests seem to be GCed correctly during snapshot expiration as verified in the existing test iceberg/core/src/test/java/org/apache/iceberg/TestRemoveSnapshots.java Lines 1040 to 1094 in 7f879b1 Also, I believe it would be great to expand the TestRemoveSnapshots coverage for v3 table as well, if you are willing to help. |
|
Thanks @dramaticlly for the suggestion - opened #17772 to extend the TestRemoveSnapshots parameterization to V3. It widens the @parameters matrix to include {3, true} and {3, false} and switches testExpireWithDeleteFiles to the existing fileADeletes() / fileBDeletes() helpers so it exercises both V2 position deletes and V3 deletion vectors. Would appreciate your review when you have a moment. |
Summary
ReachableFileCleanup#findFilesToDeleteusesManifestFiles.readPathsto enumerate live file paths for every manifest scheduled for deletion.readPathsis only defined for DATA manifests — it callsManifestFiles.readwhich assertsmanifest.content() == ManifestContent.DATAand throwsIllegalArgumentExceptionotherwise. Because the surroundingTasks.foreach(...).retry(3).suppressFailureWhenFinished()catches onlyIOExceptionexplicitly and suppresses everything else, the exception is logged per retry and swallowed, so DELETE manifests reachable only through expired snapshots are silently skipped. The delete files and DV Puffin blobs they reference are left as orphans on object storage.ReachableFileCleanupis selected byRemoveSnapshots.cleanExpirenapshotswhenever specific snapshot IDs are given, when a non-main snapshot was removed, or when there are non-main snapshots — i.e., any workflow that touches branches or tags.IncrementalFileCleanup.findFilesToDeletealready handles this correctly by usingManifestFiles.open, which returns aManifestReaderfor either DATA or DELETE manifest content. This PR alignsReachableFileCleanupwith that pattern and adds a regression test.Change
ManifestFiles.readPaths(...)calls inReachableFileCleanup#findFilesToDeletewithManifestFiles.open(...).select("file_path").liveEntries(), projecting only the file-path column so the read cost is unchanged.TestRemoveSnapshots#testReachableCleanupWithDeleteManifestFromRemovedBranchstages a position-delete file on a branch, removes the branch, expires older snapshots, and asserts the delete-file location is enumerated for deletion. Gated withassumeThat(formatVersion == 2)andassumeThat(!incrementalCleanup)to target t reachable cleanup path.Test plan
./gradlew :iceberg-core:test --tests "org.apache.iceberg.TestRemoveSnapshots"passes../gradlew spotlessCheckpasses.AI Disclosure