Skip to content

Caching (Cache-aside)

The cache-aside pattern (also called lazy loading) keeps your application in control of what goes into the cache and when. Redis sits in front of your database: if the data is there, great — skip the DB entirely. If not, fetch it from the DB and populate the cache for next time.

flowchart TD
  A["App needs cache:user:42"] --> B["GET cache:user:42"]
  B --> C{"Cache hit?"}
  C -->|"Yes"| D["Return cached value"]
  C -->|"No (nil)"| E["Query DB"]
  E --> F["SET cache:user:42 result EX 300"]
  F --> G["Return value"]
Cache-aside read flow: check Redis, fall back to the DB on a miss, then write back with a TTL

The application owns both reads and writes to the cache. Nothing is preloaded; entries are created on demand and expire automatically via the TTL.

127.0.0.1:6379> GET cache:user:42
(nil)
127.0.0.1:6379> SET cache:user:42 "{\"name\":\"Ada\",\"email\":\"[email protected]\"}" EX 300
OK
127.0.0.1:6379> TTL cache:user:42
(integer) 299
127.0.0.1:6379> GET cache:user:42
"{\"name\":\"Ada\",\"email\":\"[email protected]\"}"

EX 300 sets a 300-second (5-minute) TTL. After that time the key vanishes and the next request will be a cache miss, triggering a fresh DB read.

GET cache:user:42
SET cache:user:42 "{\"name\":\"Ada\",\"email\":\"[email protected]\"}" EX 300
TTL cache:user:42
GET cache:user:42

When the underlying data changes — a user updates their profile, for example — the cached entry is now stale. The simplest and most reliable fix is to delete the key immediately so the next read fetches fresh data from the DB.

127.0.0.1:6379> SET cache:user:42 "{\"name\":\"Ada updated\"}" EX 300
OK
127.0.0.1:6379> DEL cache:user:42
(integer) 1
127.0.0.1:6379> GET cache:user:42
(nil)
SET cache:user:42 "{\"name\":\"Ada updated\"}" EX 300
DEL cache:user:42
GET cache:user:42

A cache stampede happens when a popular key expires and dozens (or thousands) of concurrent requests all get a cache miss at the same moment. They all race to the database simultaneously, producing a sudden spike in DB load — the very thing caching was meant to prevent.

Two common mitigations: use SET ... NX so only the first writer populates the key and the rest wait, or use probabilistic early expiration — a technique that randomly refreshes a key slightly before it expires based on the remaining TTL and an estimated recomputation cost. Both strategies reduce the thundering-herd effect without requiring an external lock service.

In cache-aside, when does the app write to Redis?
Which command removes a stale cache entry?
What is a cache stampede?
Which SET option sets a TTL in seconds?