Read caching#

Hot read RPCs — the screens plus device/rack lookups — are memoized by a read-through cache that wraps the service core. A cache hit skips the recursive SQL and the SVG render entirely.

Backends#

The cache is a seam with two interchangeable backends:

  • In-process LRU (default, single replica) — CACHE_SIZE entries (default 1024), expiring after CACHE_TTL (default 30s).
  • Redis (multi-replica) — set REDIS_ADDR=host:6379. All replicas share one cache, so an invalidation on any replica is seen by all.
EnvMeaningDefault
CACHE_ENABLEDturn the read cache on/offtrue
CACHE_TTLentry time-to-live30s
CACHE_SIZELRU capacity (entries)1024
REDIS_ADDRuse Redis instead of the in-process LRU when set

Run Redis when you run more than one replica. The in-process LRU is per-pod, so a mutation on pod A leaves pod B’s cache stale until the TTL expires. A shared Redis gives global invalidation — set REDIS_ADDR on every replica. A single replica is fine on the LRU.

Graceful degradation#

The cache is never on the critical path for correctness or availability. Redis operations use fail-fast timeouts with retries disabled, and any error (a miss, a timeout, or a full outage) is treated as a cache miss — the read falls through to the database. A Redis outage therefore never returns a 5xx; reads keep being served (directly, and always fresh, since nothing is cached during the outage), and the cache self-heals when Redis returns. serve even boots with Redis down, logging a warning rather than failing startup.

Invalidation policy#

Every successful mutation (single and bulk) flushes the whole read cache. It’s coarse, but always correct for an interconnected infra graph where one change — a device move, a feed edit — can shift many screens at once (blast-radius, thermal, floor, rack, search). The TTL bounds staleness from out-of-band writes (direct SQL, the fleet generator).

Effect#

The package benchmark (go test ./internal/cache -bench Read) over a simulated 300µs screen:

BenchmarkReadUncached     348441 ns/op
BenchmarkReadCachedHit       224 ns/op    # ~1500× on a hit

Cache hit ratio is exported as a Prometheus metric (rackattack_cache_{hits,misses}_total) — see Observability. The implementation lives in internal/cache.