Skip to content

feat(transaction): add unreferenced_files detection for expire-snapshots - #3038

Open
dhruvarya-db wants to merge 2 commits into
apache:mainfrom
dhruvarya-db:feat-maintenance-unreferenced-files
Open

feat(transaction): add unreferenced_files detection for expire-snapshots#3038
dhruvarya-db wants to merge 2 commits into
apache:mainfrom
dhruvarya-db:feat-maintenance-unreferenced-files

Conversation

@dhruvarya-db

@dhruvarya-db dhruvarya-db commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Part of #2145.

The metadata side of expire-snapshots is already in place from three merged PRs: #2591 added the ExpireSnapshotsAction, #2664 made it honour the history.expire.* table properties by default, and #2667 removed statistics-file metadata for the expired snapshots. Those all edit metadata.json (dropping snapshots and their statistics) but none of them delete anything from storage, so the manifest lists, manifests, and data files that only the expired snapshots referenced are left behind.

What changes are included in this PR?

This PR adds the detection step that finds those files, without deleting anything. iceberg::transaction::unreferenced_files(table, expired_snapshot_ids) reads the manifest lists and manifests of the expiring snapshots and returns the files reachable only from them, grouped by kind (manifest lists, manifests, data files, delete files, and statistics). It mirrors Java's ReachableFileCleanup: the result is the reference-count difference files(expired) - files(retained), so anything a surviving snapshot still points at is never returned. Content files (data and deletes) are only collected when gc.enabled is set, matching drop_table_data, since they can be shared across tables; the table-private metadata is always collected. Manifests are read through ManifestReader, so encrypted manifests are handled transparently.

Any read failure aborts the whole call: a manifest list or manifest that can't be read — for a retained or an expired snapshot alike — leaves the reachable set incomplete, and continuing could delete a file that is still live. This matches Java's ReachableFileCleanup.

This is metadata analysis only — nothing is deleted here. A follow-up PR will wire this into the commit path and perform the actual file deletion.

Are these changes tested?

Add `iceberg::transaction::unreferenced_files`, which computes the files
reachable only from a set of expiring snapshots — manifest lists, manifests,
data/delete files, and statistics — so a caller can delete them after the
expire-snapshots metadata commit.

It mirrors Java `ReachableFileCleanup`: the result is the reference-count
difference `files(expired) - files(retained)`, so a file still held by any
retained snapshot is never returned. Data and delete files are only collected
when `gc.enabled` is set, matching `drop_table_data`, since they can be shared
across tables. Manifests are read through `ManifestFile::load_manifest`, so
encrypted manifests are handled transparently. Any manifest-list or manifest
read failure aborts the call, rather than risking deletion of a live file.

This is metadata analysis only; nothing is deleted here. A follow-up will wire
it into the commit path to perform the actual file deletion.
@dhruvarya-db
dhruvarya-db marked this pull request as draft August 20, 2026 21:14
Only live (added/existing) entries reference a file. A retained snapshot's
manifest can carry a data file as a Deleted entry, and counting that as
reachable made the anti-join treat the file as still live — so a file that was
deleted by a retained snapshot but had been live in an expired one was never
reported for deletion. Filter to `is_alive()` entries, matching Java's
`liveEntries()` and the scan planner's handling.
@dhruvarya-db
dhruvarya-db marked this pull request as ready for review August 20, 2026 21:31
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.

2 participants