Day 25/60: Caching with Redis -- Cache-Aside, Stampede, and Stale Reads
60 Day Go Backend Engineering Challenge
Our GET /products/{id} endpoint read Postgres on every hit. During a promo the traffic concentrated on a few hundred hot SKUs, so the same SELECT ran thousands of times a second for identical rows. p99 was 180ms, the database sat near 80% CPU, and the shared connection pool was throttling everything else. A Redis cache-aside layer cut p99 to 9ms and dropped read QPS more than 95%. Then it shipped two classic bugs. This post is both.
Cache-aside in Go
The application owns the cache. Read Redis first; on a hit, return; on a miss, read the database, populate with a TTL, return. With go-redis , a missing key is the sentinel error redis.Nil -- a normal miss, not a failure. Any other error is Redis itself misbehaving, and you fall through to the database rather than failing the request.
type Cache struct { rdb * redis . Client db * pgxpool . Pool }
func ( c * Cache ) Product ( ctx context .…