Decision records

ADR-0024: Disk tier of the response cache

  • Status: Accepted (2026-10-06)

Context

The memory cache (ADR-0012) drops entries when memory is full, cannot hold responses above max_entry_size, and is empty after every restart.

Decision

  • Optional server.cache.disk: {path, size, max_entry_size} (default entry limit: 64 MiB or size, whichever is smaller). When configured but unusable, the server does not start.
  • The disk tier receives entries evicted from memory for lack of space (moka eviction listener, cause Size) and entries larger than the memory entry limit; the store path buffers bodies up to the larger of the two limits.
  • One file per variant: magic, JSON metadata (key, status, headers, stored time as Unix seconds, initial Age, TTL, stale-while-revalidate, Vary values, tags), body. Written to a temporary file and renamed. Writes go through one writer thread with a bounded queue (256); a full queue drops the write — the request path never waits for the disk.
  • Lookups: memory first; on a miss the in-memory index (key → variants, Vary values, expiry, size, LRU counter) selects a file, read with spawn_blocking; freshness is recomputed from wall-clock time; hits that fit are promoted to memory.
  • At start the directory is indexed; expired and partial files are deleted. The tier is bounded by size (least recently used files go first, ordered by an access counter).
  • Invalidation (unsafe requests) and purge (key, prefix, tag, all) apply to both tiers.
  • Metrics: scalws_cache_disk_entries, scalws_cache_disk_bytes.

Consequences

  • An entry is findable on disk only after its asynchronous write; a lookup in between is a miss (acceptable for a cache).
  • Cache files contain response headers and bodies; the directory must be private to the service user (systemd StateDirectory or similar).