12. Caching: The Basics
12.8 How do you invalidate safely, and what's the race everyone hits?
In the kitchen: You hear the shop's price is changing. If you throw out your carton first and someone pops to the shop before the new price is on the shelf, they'll bring back a carton with the old price, and put it in your fridge for the week.
In the system: With cache-aside and write-around, a price change means "update the database, delete the cache key". The order matters.
Figure: delete the copy first, and a reader can put the old value back for the whole TTL.
The buggy order (delete, then update):
- The admin deletes
product:milkfrom the cache. - The admin starts updating the database to ₹56. It takes about 200 ms.
- During those 200 ms, a user requests milk: miss. The user's request reads the database, which still says ₹52.
- It stores ₹52 in the cache with a 10-minute TTL.
- The database update finishes, but the cache holds ₹52 for 10 minutes.
The fix: update the database first, then delete the cache key. Any reader after the delete loads the new value.
Even the right order leaves a rarer race: a slow reader could read ₹52 just before the update, then write it to the cache just after the delete. That's why every copy still gets a TTL as a safety net. For data that must never be stale, don't serve it from cache at all (12.9).
Other practical habits:
- Delete rather than overwrite the cache on a change. If two writes race, overwriting can leave the older value in place; deleting just forces a fresh read.
- If the delete fails, retry it; for important data, publish a change event so a separate worker clears the cache (Chapter 23).
Memory code: "Change the shop first, then toss the carton." And keep a use-by date on everything.