← Concepts

阅读中文版本

Cache Invalidation

Cache invalidation is the policy that decides when cached data stops being trustworthy, using TTLs, versioned keys, or explicit purge events to avoid stale reads.

performance

Definition

Cache invalidation is the process of removing or replacing cached data when the underlying source changes or when the data is no longer fresh enough to serve. Common strategies include time-to-live expiry, versioned or content-addressed keys, write-through updates, and explicit purge on mutation. The strategy must match the consistency requirement: some data tolerates minutes of staleness, while account balances or permissions may not.

Context and scope

Caches improve latency and reduce load, but every cache introduces a window where readers may see old data. Invalidation is the hard part of caching because the cache does not automatically know when the source changed. Distributed caches add races between writers, cache purges, and concurrent readers.

Common misconceptions

  • A short TTL removes the need for invalidation — it bounds staleness but can still serve incorrect data for the TTL window
  • Deleting the cache after writing is race-free — a concurrent reader can repopulate stale data between the write and the delete
  • Cache invalidation means clearing the entire cache — targeted keys or versions are usually safer and cheaper

Anti-patterns

  • Caching authorization decisions without a version tied to permission changes
  • Using random TTLs to avoid stampedes without addressing correctness
  • Purging a key before the database transaction commits, allowing readers to repopulate old values

Related concepts

Related examples

Authoritative source

https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching

Strategies

TTL expiry bounds staleness with no coordination: simple, but correctness depends on the TTL being acceptable. Versioned keys encode a version or content hash in the key, so a new version is a cache miss and the old entry becomes unreachable. Explicit purge removes keys after a mutation but requires ordering and retry logic. Write-through updates the cache in the same path as the write, trading write latency for fresher reads.

const cache = new Map();

async function getUser(id, version) {
  const key = `user:${id}:v${version}`;
  if (cache.has(key)) return cache.get(key);
  const user = await db.users.findById(id);
  cache.set(key, user);
  return user;
}

// A mutation increments the version, making old keys unreachable.
async function renameUser(id, name, currentVersion) {
  await db.users.update(id, { name });
  return currentVersion + 1;
}

The write/delete race

A common sequence is: writer updates the database, deletes the cache key; a reader that fetched the old value before the update writes it back after the delete. Using versioned keys avoids this because the reader writes to the old version, which new readers no longer use. If you use explicit deletes, delete after commit, consider a short delayed second delete, or use a lock.

When to use which

Use TTLs for data where bounded staleness is acceptable, such as public content. Use versioned keys or explicit invalidation for permissions, account state, and inventory. Compared to no cache, invalidation adds correctness work; compared to a database read on every request, it trades freshness for latency and load.

Pitfalls

Invalidation events can be lost; pair them with retries and a TTL as a backstop. Caches shared across services need a clear owner for each key. Instrument hit rate, stale-read errors, and purge failures; a cache that is fast but wrong is worse than no cache.