PhoenixKitOG.Render.Cache (PhoenixKitOG v0.2.0)

Copy Markdown View Source

On-disk cache for rendered OG PNGs.

Cache key = SHA-256 of (template_uuid, template_updated_at, canvas, binding_values). Two different posts with the same resolved values share a single file; editing the template invalidates every dependent cache entry automatically (the template's updated_at is part of the key).

Files live under System.tmp_dir!()/phoenix_kit_og_cache/ — a writable, ephemeral scratch dir (NOT priv/static, which is read-only in a release), read back by PhoenixKitOG.Web.ImageController at /og-image/:key and streamed as image/png. Filename pattern:

<16-hex hash>.png

The full hash is 64 hex; we truncate to 16 (64 bits) which is collision-resistant for our scale (millions of templates × posts).

Eviction

Every template edit mints new keys (the updated_at is hashed in) and orphans the old files, so the dir grows monotonically without a bound. write/2 therefore calls maybe_prune/0 on a small fraction of writes (cheap amortized): it deletes files older than @ttl_seconds and, if the count still exceeds @max_files, the oldest beyond the cap. A still-valid render just re-renders on its next miss — the cache is a performance layer, never a source of truth. Tune via config :phoenix_kit_og, cache_ttl_seconds: _, cache_max_files: _.

Summary

Functions

Clears every cached render — useful after upgrading the renderer.

Returns {cache_key, absolute_path}. The path may or may not exist — call exists?/1 or read/1 to check.

Deletes cache files older than the TTL and, if still over the count cap, the oldest beyond it. Safe to call from a cron or by hand; write/2 calls it probabilistically. Never raises — a prune failure must not break a render.

Writes the PNG bytes to the cache file. Atomic: write to a tempfile in the same dir then rename, so a concurrent read never sees a partial file.

Functions

clear()

@spec clear() :: :ok

Clears every cached render — useful after upgrading the renderer.

exists?(key)

@spec exists?(String.t()) :: boolean()

key_and_path(template, context)

@spec key_and_path(map(), map()) :: {String.t(), String.t()}

Returns {cache_key, absolute_path}. The path may or may not exist — call exists?/1 or read/1 to check.

prune()

@spec prune() :: :ok

Deletes cache files older than the TTL and, if still over the count cap, the oldest beyond it. Safe to call from a cron or by hand; write/2 calls it probabilistically. Never raises — a prune failure must not break a render.

read(key)

@spec read(String.t()) :: {:ok, binary()} | {:error, term()}

write(key, png_bytes)

@spec write(String.t(), binary()) :: :ok | {:error, term()}

Writes the PNG bytes to the cache file. Atomic: write to a tempfile in the same dir then rename, so a concurrent read never sees a partial file.