Concept · Caching
Cache invalidation
Delete on write, version your keys, and know the race that leaves stale data behind.
3 minstale cachecache consistency
Invalidation is how a cache learns that its copy is out of date. Get it wrong and users see old prices, permissions or profiles until the entry happens to expire.
The toolkit
- TTLs on everything. The TTL is the upper bound on staleness when every other mechanism fails. Pick it from the business question: how wrong can this be, and for how long?
- Delete on write, after the commit. Deleting before the commit lets a concurrent reader re-cache the old value. Deleting (rather than writing the new value) avoids racing writers leaving the older value in the cache.
- Versioned keys. Put a version in the key (
product:v3:42, or a per-entity version counter). Bumping the version makes old entries unreachable at once, which is the easiest way to invalidate keys derived from many rows. - Reliable delivery of invalidations. An inline
DELafter the commit is lost if the process crashes in between. Driving invalidations from an outbox or change data capture makes them at-least-once (transactional outbox).
The race that remains
With cache-aside, even delete-after-commit leaves a window:
reader A: cache miss → reads v1 from the database
writer B: updates row to v2 → commits → deletes the cache key
reader A: (delayed) SET cache = v1 ← stale until the TTL expiresMitigations, from simple to thorough:
- Short TTLs for data where this matters.
- Delayed double delete: delete again a second or two after the write.
- Leases: on a miss, the cache hands out a token; a later delete invalidates it, and a
SETwith an invalid token is ignored. Facebook's memcache design uses this. - Bypass the cache for reads that must be fresh (a payment balance).
Invalidate the outer layers too
A browser or CDN copy can't be deleted from your server. Use versioned URLs for static assets, short max-age values for anything mutable, and CDN purge APIs for urgent removals (content delivery networks).