Files
booth/persistent-memory.d/2026-09-23-the-cache-that-aged-the-thing-it-cached.md
T
vh 70bfff15cf memory: the cache that aged the thing it cached
Two lessons from the thumbnail work, the second of which nearly shipped.

We parked progressive loading on a count of images and the cost was in bytes.
'Measure the real booth before optimising it' was followed and still gave the
wrong answer, because we measured the dimension that was easy to measure rather
than the one the user feels.

And a cache living inside the thing it describes can age that thing. Excluding
every path under the cache dir passed its own test and was still wrong: creating
the directory touches the BOOTH's own mtime, which is what _newest_mtime seeds
from. The contents were excluded; the existence was the leak. Had it reached the
Desk, one index load would have pushed every booth's expiry out and the TTL
would never have fired again.
2026-09-23 10:58:21 -07:00

3.0 KiB
Raw Blame History

The cache that aged the thing it cached

2026-09-23 · booth

Thumbnails: 77.5 MB → 0.78 MB on the biggest gallery, ~100 MB → 1.12 MB on the Desk. The operator found the defect in about a minute of using the live redesign. Two lessons, and the second nearly shipped.

⚠ LESSON 1 — we parked it on a count and the cost was in the bytes

ROADMAP parked progressive loading on "the largest gallery is 66 images; at that size a lazy grid is almost certainly fine", and the parking-lot row said "270 <img loading=lazy> may be fine." Both count IMAGES. Neither weighs BYTES. 66 is a fine count sitting on 77.5 MB of 1024×1024 PNGs rendered at 250px — roughly 16× the pixels that reach the screen.

The rule "measure the real booth before optimising it" was followed and still produced the wrong answer, because we measured the dimension that was easy to measure rather than the one that determines the experience. Before parking anything on a measurement again, ask what the user would actually feel.

⚠ LESSON 2 — a cache INSIDE the thing it describes can age it, and excluding the cache is not enough

.thumbs/ lives inside the booth on purpose, so it is swept with the booth and can never outlive what it describes. But _newest_mtime — which feeds is_expired, which feeds rmtree — walks the booth, so the server writing a cache on a mere view counted as the operator touching the booth.

The first fix passed its own test and was still wrong. Excluding every path under THUMB_DIR does not help, because creating the directory touches the BOOTH DIRECTORY's own mtime, and _newest_mtime SEEDS from exactly that. The cache's contents were excluded; its existence was the leak.

The fix restores the booth's stamp across the mkdir. That cannot hide real activity: any file an agent adds is counted by its own mtime in the same walk, so the directory stamp is only a seed.

The blast radius if it had reached the Desk. The Desk pulls a preview thumbnail per booth, so ONE INDEX LOAD would have pushed EVERY booth's expiry out — the TTL would never fire again and nothing would ever sweep. Caught by design-dev from the outside, hours before the strip landed; verified after the fix on the live set: 29 booths, two Desk loads, 22 caches generated, 0 clocks moved.

.viewed counting as activity is different and deliberate — that is a record of a person looking, which U4 says is activity. A server-written derived cache is machinery, like the .lock sidecars already excluded. Any future cache inherits this: exclude it from the clock AND preserve the parent's stamp.

A third, smaller one: the dot-namespace was only skin deep

booth_items and zip_booth both tested p.name.startswith(".") — the FILE's name — so .thumbs/a.png (name a.png) would have rendered as a gallery item and shipped inside every zip download. CLAUDE.md invariant 2 promises a dotfile costs nothing in item counts, galleries or zips; that was true only at the top level. Both now skip every dot-prefixed path COMPONENT.