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_SIZEentries (default1024), expiring afterCACHE_TTL(default30s). - Redis (multi-replica) — set
REDIS_ADDR=host:6379. All replicas share one cache, so an invalidation on any replica is seen by all.
| Env | Meaning | Default |
|---|---|---|
CACHE_ENABLED | turn the read cache on/off | true |
CACHE_TTL | entry time-to-live | 30s |
CACHE_SIZE | LRU capacity (entries) | 1024 |
REDIS_ADDR | use 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_ADDRon 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 hitCache hit ratio is exported as a Prometheus metric
(rackattack_cache_{hits,misses}_total) — see Observability.
The implementation lives in
internal/cache.