Skip to content

Make garbage collection reachability path-aware#97

Merged
kmatzen merged 1 commit into
assorted-correctness-nitsfrom
path-aware-gc
Jul 19, 2026
Merged

Make garbage collection reachability path-aware#97
kmatzen merged 1 commit into
assorted-correctness-nitsfrom
path-aware-gc

Conversation

@kmatzen

@kmatzen kmatzen commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Fixes #87, taking the path-aware-GC option rather than content-addressed storage. Stacked on #96.

The mismatch

Objects live at assets/{hash}/{manifest_key}.gz, so the storage unit is hash + path. GC judged reachability on the hash alone. Those disagree, and it leaked in both directions:

  • a/x.bin and b/x.bin share hash h; remove a/x.bin. h is still referenced by b/x.bin, so assets/h/a/x.bin.gz was never collected.
  • Rename a file and assets/h/oldpath.gz stayed reachable-by-hash forever, though nothing could address it.

Reachability is now the set of base keys the manifest implies. A key is live if it is one of those, or a .chunkN belonging to one.

Knock-on changes

The in-flight registry moves from hashes to base keys. Otherwise a claim on one path would protect a different path sharing its content — the same conflation, reintroduced through the registry added in #94.

remove_file / remove_subtree now delete chunks. They deleted only the base .gz, orphaning every chunk of a large file. Previously those orphans were also uncollectable when the hash stayed referenced elsewhere; now they would be collected eventually, but --purge-from-s3 should mean now.

What this deliberately does not do

Deduplication stays per-path: two identical files at different paths still occupy two objects. The README's claim that identical files are stored only once remains inaccurate. Fixing that means content-addressed storage (assets/{hash}.gz) and a migration, which is the option not taken here. Either the layout or the documentation should change; I'd suggest correcting the docs unless the storage saving matters.

A regression an existing test caught

An earlier draft collected any key under the prefix that wasn't in the live set, including malformed ones. test_cleanup_s3_short_key_paths failed and was right to: s3lfs didn't put those there in a form it recognises, so they are not ours to delete. Keys must now have the shape of an asset before being considered — restoring the intent of the old len(parts) < 3 guard.

Verification

Five of the seven new tests fail on the parent commit. Three tests from #94 were updated for the key-based API.

61 failed / 561 passed61 failed / 568 passed, failure set identical. The 61 are the missing-python-binary failures fixed in #93.

🤖 Generated with Claude Code

https://claude.ai/code/session_019UrDtdFKGwQZp8oYGwL2rj

Objects are stored at assets/{hash}/{manifest_key}.gz, so the storage unit
is hash plus path. Garbage collection judged reachability on the hash
alone, which leaked in both directions: an object stayed reachable as long
as any path shared its content, so removing one of two identical files
never freed the removed path's object, and renaming a file left its old
object reachable forever though nothing could address it.

Reachability is now computed as the set of base keys the manifest implies,
and a key is live if it is one of those or a .chunkN belonging to one. The
in-flight registry moves from hashes to the same base keys, so a claim on
one path no longer protects a different path that happens to share content.

remove_file and remove_subtree deleted only the base .gz key, orphaning
every chunk of a large file. They now delete the chunks too.

This keeps the existing layout rather than moving to content-addressed
storage, so no migration is needed. It does mean deduplication remains
per-path: two identical files at different paths still occupy two objects,
contrary to what the README implies. That claim should be corrected or the
layout changed; both are out of scope here.

An existing test caught a regression in an earlier draft: keys under the
prefix that do not have the shape of an asset were being collected. They
are now explicitly skipped, since s3lfs did not put them there in a form it
recognises and they are not ours to delete.

Five of the seven new tests fail on the parent commit. Three tests from the
in-flight registry change were updated for the key-based API.

Test suite: 61 failed / 561 passed before, 61 failed / 568 passed after.
Failure set identical.

Fixes #87

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kmatzen
kmatzen merged commit c3ccd26 into assorted-correctness-nits Jul 19, 2026
3 checks passed
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.

1 participant