The resized-image cache grows without bound #188

Closed
opened 2026-08-22 22:26:32 +02:00 by Claude · 1 comment
Collaborator

365cd2c keeps every re-encoded picture under
<cache>/images/resized-images/ and nothing ever removes one. A library
browsed at several sizes accumulates an entry per picture per size, and a
replaced picture leaves its old entry behind by design — the key carries the
source's modified time, so a new file lands on a new name rather than over the
old one.

Upstream keeps the same directory and sweeps it from a scheduled task. Ours has
no such task, so the directory only grows.

Worth settling: what the age or size ceiling is, whether the sweep is a
scheduled task beside the others or something the cache does for itself, and
whether an entry whose source no longer exists is worth detecting or simply
ages out like the rest.

How to know it is done

A cache directory holding an entry older than the ceiling does not hold it
after the sweep runs, and one holding only fresh entries is untouched.

House rules

Tests run under cargo nextest; upstream's source is the checkout in
upstream/ at the pin.

`365cd2c` keeps every re-encoded picture under `<cache>/images/resized-images/` and nothing ever removes one. A library browsed at several sizes accumulates an entry per picture per size, and a replaced picture leaves its old entry behind by design — the key carries the source's modified time, so a new file lands on a new name rather than over the old one. Upstream keeps the same directory and sweeps it from a scheduled task. Ours has no such task, so the directory only grows. Worth settling: what the age or size ceiling is, whether the sweep is a scheduled task beside the others or something the cache does for itself, and whether an entry whose source no longer exists is worth detecting or simply ages out like the rest. ## How to know it is done A cache directory holding an entry older than the ceiling does not hold it after the sweep runs, and one holding only fresh entries is untouched. ### House rules Tests run under `cargo nextest`; upstream's source is the checkout in `upstream/` **at the pin**.
Author
Collaborator

It was already swept; what was missing was the evidence. dd09267.

The resized-image directory is under the server's cache directory, and
DeleteCacheFiles — Kind::SweepCache, a daily trigger — walks that whole tree
and deletes everything last written more than thirty days ago, which is
upstream's own retention (DeleteCacheFileTask.cs:83-105). Both sides compute
the same path: serve.rs:308 hands paths.on_disk(&paths.cache) to the store
and maintenance.rs:76 sweeps the same value, with the substitution applied
once before either reads it.

So the answers to what this issue asked to settle are upstream's: thirty days,
the scheduled task that already exists, and an entry whose source is gone ages
out like the rest rather than being hunted for.

The test that proves it — the_resized_pictures_go_with_the_rest_of_the_cache —
ages two entries past the ceiling and leaves a third inside it, and it is bound
to the store's own path rather than to a re-spelling of it: moving the directory
out from under the cache fails it left: 0, right: 2.

A review turned up two real things beside it, fixed in 594f7c2:

  • The sweep swallowed what it could not do. A directory it cannot read and a
    file it cannot delete were skipped silently and the run reported success — the
    one way the directory really does grow without bound. Both are logged now, as
    upstream logs them.
  • Two requests that missed the same entry shared one partial name, so one
    writer's truncate could land between the other's write and its rename. Each
    writer names its own now, as the metadata writer beside it already did.
**It was already swept; what was missing was the evidence.** `dd09267`. The resized-image directory is under the server's cache directory, and `DeleteCacheFiles` — `Kind::SweepCache`, a daily trigger — walks that whole tree and deletes everything last written more than thirty days ago, which is upstream's own retention (`DeleteCacheFileTask.cs:83-105`). Both sides compute the same path: `serve.rs:308` hands `paths.on_disk(&paths.cache)` to the store and `maintenance.rs:76` sweeps the same value, with the substitution applied once before either reads it. So the answers to what this issue asked to settle are upstream's: thirty days, the scheduled task that already exists, and an entry whose source is gone ages out like the rest rather than being hunted for. The test that proves it — `the_resized_pictures_go_with_the_rest_of_the_cache` — ages two entries past the ceiling and leaves a third inside it, and it is bound to the store's own path rather than to a re-spelling of it: moving the directory out from under the cache fails it `left: 0, right: 2`. A review turned up two real things beside it, fixed in `594f7c2`: - **The sweep swallowed what it could not do.** A directory it cannot read and a file it cannot delete were skipped silently and the run reported success — the one way the directory really does grow without bound. Both are logged now, as upstream logs them. - **Two requests that missed the same entry shared one partial name**, so one writer's truncate could land between the other's write and its rename. Each writer names its own now, as the metadata writer beside it already did.
Sign in to join this conversation.
No description provided.