Decision records

ADR-0012: Bounded in-memory response cache

  • Status: Accepted (2026-10-05)

Context

Handoff §13: bounded memory cache first, optional disk cache second; correct keys, Vary handling, stale-while-revalidate where safe, tag/key purge and profile-driven bypass. Handoff §8: never infer personalised-content cacheability from AI.

Decision

  • Opt-in per application (cache.enabled). One server-wide memory budget (server.cache.memory), entries above server.cache.max_entry_size are not stored. Storage is moka (TinyLFU admission, size-weighted eviction), not a hand-written LRU.
  • Only explicit freshness: s-maxage, max-age or Expires from the response, or an operator-configured default_ttl for 200 responses without any freshness information. No heuristic freshness.
  • Bypassed entirely (no lookup, no store): methods other than GET/HEAD, requests with Authorization, Range, or Cache-Control: no-cache | no-store / Pragma: no-cache.
  • Never stored: responses with Set-Cookie, Cache-Control: private | no-store | no-cache, Vary: *, non-cacheable statuses, or bodies that did not complete. Requests matching the application’s bypass rules (profile cache_bypass plus configuration: path prefixes, cookie-name prefixes, query parameters) skip the cache entirely.
  • Key: normalised host + raw path and query; each key holds up to 8 variants distinguished by the request values of the headers named in Vary.
  • HEAD is answered from a GET entry. A successful unsafe request (POST, PUT, PATCH, DELETE) invalidates the key of its URL (RFC 9111 §4.4).
  • stale-while-revalidate (response directive or cache.stale_while_revalidate): within the window a stale entry is served while one background request refreshes it (single flight per key); must-revalidate disables it.
  • Purge by URL, URL prefix, Cache-Tag or everything through POST /cache/purge on the loopback metrics/admin listener (until the admin API, M9).
  • Responses carry X-Cache: HIT|MISS|STALE|BYPASS and Age on hits.
  • Disk tier: deferred until the memory tier is proven (handoff §13).

Consequences

  • Applications must emit correct Cache-Control to benefit; nothing personalised is cached by guesswork.
  • Request coalescing (collapsing concurrent misses) is not implemented yet.