Cache-aside and stale data
Not climbed yetModel answer
The application talks to both the cache and the database; the cache never talks to the database itself.
Read: look in the cache. On a hit, return it. On a miss, read from the database, put the result in the cache with a TTL, and return it.
Write: write to the database, then remove (or update) the cache entry so the next read fetches the fresh value.
It is the most common pattern because the cache only ever holds data someone asked for, and if the cache goes down the app still works, just slower.
Model answer
Usually the write never touched the cache and the old entry lives until its TTL runs out, or one code path updates the database without clearing the cache.
Delete is the safer choice. If two writers update the database and then each sets the cache, the sets can land in the opposite order to the database writes, leaving the cache holding the older value indefinitely. Deleting is the same whatever the order: the next reader loads whatever the database holds.
Delete *after* the database commit, not before; deleting first lets a reader refill the cache with the old value in the gap.
Model answer
Reader R misses the cache and reads the old value from the database. Before R writes it to the cache, writer W updates the database and deletes the cache key. Then R, a little late, puts the old value into the cache. It now stays stale until the TTL.
Ways to close or shrink it:
- Always set a TTL, so any stale entry heals itself. This alone is enough for most data.
- Store a version with the value and only write if it is newer (a Lua script in Redis can compare and set).
- Delayed double delete: delete again a second or so after the write.
- Invalidate from the database's change stream (CDC, for example Debezium), so deletes follow the commit order.
Model answer
A cache stampede (thundering herd): every request in the next few hundred milliseconds misses at once, and thousands of identical queries hit the database together, sometimes taking it down, which then keeps the cache empty.
Fixes:
- Single-flight: only one request rebuilds the value, using a short lock (
SET lock NX PX 5000); the others wait briefly or get the old value. - Stale-while-revalidate: keep serving the expired value while one background job refreshes it.
- Early refresh: refresh a hot key before it expires, either on a timer or with probability rising as expiry nears.
- Jittered TTLs, so keys cached at the same moment do not all expire at the same moment.
- Single-flight: only one request rebuilds the value, using a short lock (
