# Cache invalidation

> Delete on write, version your keys, and know the race that leaves stale data behind.

Concept · Caching · https://levelcode.org/concepts/cache-invalidation

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

1. **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?
2. **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.
3. **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.
4. **Reliable delivery of invalidations.** An inline `DEL` after 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](https://levelcode.org/concepts/transactional-outbox)).

## The race that remains

With cache-aside, even delete-after-commit leaves a window:

```text
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 expires
```

Mitigations, 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 `SET` with 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](https://levelcode.org/concepts/cdn)).
