SysDesignPrep.com
Study guide 23 of 183

Cache invalidation strategies

Keeping caches correct when data changes: TTLs, delete on write, write-through and write-behind, the race conditions that leave stale data, versioned keys, invalidation from change data capture, leases against stampedes, and multi-layer caches.

Reading is half of it. See this used in a real interview: walk through Design a Distributed Cache →

"There are only two hard things in computer science: cache invalidation and naming things." Adding a cache is easy; keeping it from serving stale or wrong data after updates is the hard part. Interviewers who hear "we add a Redis cache" often follow with "what happens when the data changes?" This guide gives you the patterns, the races that break them, and the fixes.

Start with how stale is acceptable

DataTolerable stalenessApproach
Static assets, immutable mediaforeverversioned URLs, no invalidation
Popular lists, recommendations, countsseconds to minutesTTL only
Profiles, product pagessecondsinvalidate on write plus TTL as safety net
Prices, inventory, permissionsnone at decision timeread source of truth for decisions; cache only for display
Balances, paymentsnonedo not cache, or cache with versioned reads

Answering this first often makes invalidation trivial.

TTL only

Every entry expires after a fixed time. Simple, self-healing, and bounded staleness. Add jitter to TTLs so many keys do not expire at the same moment. Always set a TTL, even when you also invalidate explicitly, as a safety net for missed invalidations.

Delete on write (cache-aside)

The application updates the database, then deletes the cache key; the next read misses and reloads fresh data. Deleting is safer than updating the cache with the new value, because concurrent writers updating the cache can leave it with an older value.

Even delete-on-write has a race:

  1. Reader A misses the cache and reads the old value from the database.
  2. Writer B updates the database and deletes the cache key.
  3. Reader A writes the old value into the cache.

The stale value now lives until its TTL. Fixes:

  • Leases: on a miss, the cache gives the reader a lease token; a delete invalidates outstanding leases, so A's late write is rejected. (Facebook's Memcache paper describes this.)
  • Versioned values: store the row's version with the cached value and refuse to overwrite a newer version with an older one.
  • Delayed second delete: delete again shortly after the write. Crude, but narrows the window.
  • Short TTLs to bound the damage.

Write-through and write-behind

  • Write-through: writes go to the cache and the database together (often through a cache layer that writes to the database). Reads after writes are fresh; every write pays both costs.
  • Write-behind (write-back): writes go to the cache, which flushes to the database asynchronously. Very fast writes, but data can be lost if the cache fails before flushing; use only where that is acceptable (counters, analytics).

See caching.

Invalidation from the database log

Instead of every code path remembering to invalidate, subscribe to the database's change stream: change data capture emits each committed change, and an invalidator deletes or refreshes the affected keys. Benefits: no missed invalidations from scripts or other services, ordering per row, and invalidation only after commit. The cost is a short delay (typically well under a second).

Versioned keys

Embed a version in the key (user:42:v17) and store the current version somewhere cheap (or in the parent object). Updating means incrementing the version; old keys are simply never read again and expire. Works well for derived data with many keys (all of a user's rendered fragments), since one version bump invalidates them all. The same idea underlies fingerprinted asset URLs. See HTTP caching.

Stampedes on invalidation

Invalidating a hot key sends every request to the database at once. Protect with:

  • Request coalescing: one request recomputes; others wait for its result.
  • Stale-while-revalidate: keep serving the old value while one background refresh runs.
  • Early probabilistic refresh: refresh before expiry with increasing probability as expiry approaches.

See hot keys and skew.

Multi-layer caches

Browser, CDN, application in-process cache, distributed cache, database buffer cache: each layer needs an invalidation story. In-process caches across hundreds of servers are hardest; use short TTLs or broadcast invalidations via pub/sub, and accept a few seconds of inconsistency between servers. CDN layers use purges or versioned URLs. See CDN and edge.

Derived and aggregated data

Caches of computed results (feeds, search results, counts) depend on many rows. Rather than invalidating precisely, recompute on a schedule or on events, accept short staleness, and patch in the user's own changes for read-your-writes. See fan-out on write vs read and consistency models.

In the interview

"Cache-aside with delete on write, invalidations driven by CDC so no path is missed, a TTL of five minutes as a safety net, leases to prevent stale backfills, and request coalescing for hot keys. Prices are read from the database at checkout regardless of the cache." See Design a Distributed Cache.

Checklist

  • Staleness tolerance decided per data type.
  • TTLs with jitter on everything.
  • Delete rather than update on write; leases or versions against races.
  • CDC-driven invalidation for completeness.
  • Versioned keys for groups of derived entries.
  • Coalescing and stale-while-revalidate against stampedes.
  • An invalidation plan for every cache layer; decisions made on the source of truth.

Open in your browser to sign in

Google does not allow sign-in inside this app's built-in browser. Open this page in Safari and sign in there. The link opens this same page.

Tap the ⋯ or share button at the top or bottom of the screen, then Open in browser. Or copy the link and paste it into Safari.