
Cache Invalidation
Cache invalidation feels hard because the cache is usually modeling several read shapes over data that changes for several reasons. If those relationships are not explicit, freshness becomes guesswork. TTL is one tool. Real systems usually need a combination of direct key invalidation, tag fan-out, and background refresh.
Minimal Example
const tags = [
`product:${product.id}`,
`collection:${product.collectionId}`,
];
await cache.set(`product:${product.id}`, product, { tags, ex: 300 });
await cache.invalidateTags(tags); What It Solves
- Lets you trade storage and recomputation for faster reads without permanent staleness.
- Keeps several materialized views in sync when one domain object changes.
- Makes freshness policy explicit instead of hiding it inside random TTLs.
Failure Modes
- Choosing keys that match one endpoint but not the underlying data dependency graph.
- Using TTL as a substitute for event-driven invalidation on frequently updated entities.
- Forgetting derived collections and search results when a source record changes.
Production Checklist
- Name keys and tags from domain entities, not only from URLs.
- Document which writes invalidate which read models.
- Measure stale-read tolerance so invalidation cost matches business reality.
Closing
Cache invalidation becomes tractable when key design and write paths are designed together. If they are not, freshness bugs are inevitable.









