From b8b3b24ffa1ecab9cef5e798498e8d7b019e5daf Mon Sep 17 00:00:00 2001 From: Claudio Ortolina Date: Thu, 30 Apr 2026 13:05:59 +0100 Subject: [PATCH] ML-151: document Assets.Cache TTL and invalidation strategy Expand @moduledoc with cache key structure, 7-day TTL via @one_week_seconds, periodic pruning by PruneAssetCache (every 12h), and rationale for TTL-only invalidation (content-addressable immutable assets). Update architecture.md entry accordingly. --- ...che-TTL-and-cache-invalidation-strategy.md | 37 +++++++++++++++++-- docs/architecture.md | 2 +- lib/music_library/assets/cache.ex | 31 ++++++++++++++++ 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/backlog/tasks/ml-151 - Document-Assets.Cache-TTL-and-cache-invalidation-strategy.md b/backlog/tasks/ml-151 - Document-Assets.Cache-TTL-and-cache-invalidation-strategy.md index bf880d76..adcd392b 100644 --- a/backlog/tasks/ml-151 - Document-Assets.Cache-TTL-and-cache-invalidation-strategy.md +++ b/backlog/tasks/ml-151 - Document-Assets.Cache-TTL-and-cache-invalidation-strategy.md @@ -1,9 +1,10 @@ --- id: ML-151 title: Document Assets.Cache TTL and cache invalidation strategy -status: To Do +status: Done assignee: [] created_date: '2026-04-30 10:48' +updated_date: '2026-04-30 12:04' labels: - documentation - assets @@ -28,7 +29,35 @@ Add a `@moduledoc` to `Assets.Cache` that explains: ## Acceptance Criteria -- [ ] #1 `Assets.Cache` `@moduledoc` documents the TTL value and where it is configured -- [ ] #2 The invalidation strategy (TTL-based expiry) is explained with rationale -- [ ] #3 Any architectural implications are captured in `docs/architecture.md` +- [x] #1 `Assets.Cache` `@moduledoc` documents the TTL value and where it is configured +- [x] #2 The invalidation strategy (TTL-based expiry) is explained with rationale +- [x] #3 Any architectural implications are captured in `docs/architecture.md` + +## Implementation Plan + + +## Plan + +### What we're documenting + +`Assets.Cache` is an ETS-based cache for transformed binary asset data. Assets are stored by SHA256 hash and are immutable (write-once). The cache key is `{payload, format}`. + +### Key facts to document + +- **TTL**: `@one_week_seconds` = 7 days, defined as a module attribute +- **Pruning**: `prune/0` defaults to 7-day TTL, called every 12h by `PruneAssetCache` Oban cron worker +- **Invalidation strategy**: TTL-based expiry only — no explicit invalidation + +### Rationale + +Explicit invalidation isn't needed because: +1. Assets are content-addressable (SHA256) and immutable — updates create new hashes, so old entries are never requested again +2. Periodic pruning (every 12h) cleans up stale entries +3. ETS table is in-memory, cleared on restart + +### Changes + +1. Expand `@moduledoc` in `lib/music_library/assets/cache.ex` +2. Expand the `Assets.Cache` entry in `docs/architecture.md` Business Logic Modules table + diff --git a/docs/architecture.md b/docs/architecture.md index 0d3099bf..68429a33 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -123,7 +123,7 @@ Last.fm schemas (separate, not Ecto-persisted to main DB): | `Req.RateLimiter` | ETS-backed Req request step enforcing per-API minimum intervals between requests | | `Req.RateLimiter.Clock` | Behaviour for time operations (allows test clock injection) | | `Req.RateLimiter.SystemClock` | Real clock implementation using System.monotonic_time | -| `Assets.Cache` | ETS-based asset cache with TTL | +| `Assets.Cache` | ETS-based asset cache with TTL (7-day TTL, TTL-only invalidation since assets are content-addressable and immutable) | | `Assets.Image` / `Assets.Transform` | Image processing via Vix (libvips) | | `Colors.Extractor` | Behaviour for dominant color extraction (configurable, allows test stubbing) | | `Colors.KMeansExtractor` | Color extraction via K-Means clustering (dominant_colors library), implements `Colors.Extractor` | diff --git a/lib/music_library/assets/cache.ex b/lib/music_library/assets/cache.ex index 7671c035..68f02fce 100644 --- a/lib/music_library/assets/cache.ex +++ b/lib/music_library/assets/cache.ex @@ -1,6 +1,37 @@ defmodule MusicLibrary.Assets.Cache do @moduledoc """ ETS-based asset cache with TTL for serving frequently accessed images. + + ## Cache key + + Each entry is keyed by `{payload, format}` where `payload` is a transform + parameter string (encoding width and asset hash) and `format` is the output + MIME type (e.g. `"image/webp"`). + + ## TTL and pruning + + The TTL is configured via the `@one_week_seconds` module attribute + (`60 * 60 * 24 * 7` = 7 days). The `prune/0` function deletes all entries + whose `inserted_at` timestamp is older than this threshold. + + Pruning runs automatically every 12 hours via the `PruneAssetCache` Oban + cron worker. + + ## Invalidation strategy + + This cache uses **TTL-based expiry only** — there is no explicit + invalidation. This is sufficient because: + + 1. Assets are content-addressable (SHA256 hashes) and immutable once + written. When an image is replaced (e.g. a new cover is uploaded), + the new asset gets a new hash, producing a different cache key. The + old entry is never requested again and naturally expires. + + 2. The ETS table is in-memory and cleared on application restart. + + Explicit invalidation would add complexity (tracking which cache entries + derive from which original asset hash) with no practical benefit given + the above properties. """ @spec new() :: :ets.table()