Cache Invalidation Strategies
Architecture Diagram
The Five Patterns
Cache invalidation has a reputation as one of the two hard problems in computer science. The difficulty is not in the mechanics but in choosing the right pattern for your consistency requirements.
Cache-aside (lazy loading): The application reads from cache. On miss, it reads from the database and writes the result to cache. Simple and safe. The downside is that the first request after a cache miss is slow, and you can serve stale data between a database write and cache expiration.
Write-through: Every write goes to both the cache and the database. Reads always hit the cache. Consistency is strong, but write latency increases because you wait for both writes to complete. DynamoDB Accelerator (DAX) uses this pattern.
Write-behind (write-back): Writes go to the cache immediately, and the cache asynchronously flushes to the database. This gives you fast writes but risks data loss if the cache node dies before flushing. Use this only for data you can afford to lose, like view counts or activity feeds.
Read-through: Similar to cache-aside, but the cache itself handles fetching from the database on miss. The application only talks to the cache layer. This simplifies application code but requires cache infrastructure that supports data source integration.
Refresh-ahead: The cache proactively refreshes entries before they expire, based on access frequency. This eliminates cache miss latency for hot keys. Caffeine (Java) and Redis with custom Lua scripts support this pattern.
Preventing Cache Stampede
When a popular cache key expires, every concurrent request sees a cache miss and hits the database. Facebook documented this problem at scale: a single celebrity post cache expiration could generate thousands of simultaneous database queries.
Three solutions work in practice. Locking: The first request that sees a miss acquires a lock and refreshes the cache. Other requests wait or get a slightly stale value. Probabilistic early expiration: Each request has a small random chance of refreshing the cache before TTL expires, spreading the load. Background refresh: A separate process monitors popular keys and refreshes them before expiration, so the application never sees a miss.
Event-Driven Invalidation
The cleanest approach for write-heavy systems is Change Data Capture. Debezium reads the database's write-ahead log and publishes change events to Kafka. A consumer service listens for relevant changes and invalidates or updates the corresponding cache entries. This decouples the write path from cache management entirely.
The latency between a database write and cache invalidation is typically 50-200ms with CDC. For most applications, this window is acceptable.
CDN Cache Purging
CDN caches add another layer of complexity. Cloudflare purges propagate globally in under 30 seconds. Fastly offers instant purging via surrogate keys (cache tags), which lets you tag cached responses and purge all responses sharing a tag in one API call. If your application serves user-generated content through a CDN, surrogate keys are worth the integration effort.
Key Points
- •Cache-aside (lazy loading) is the safest default pattern. The application checks cache first, falls back to the database, and populates cache on miss
- •TTL-based expiration is simple but trades freshness for simplicity. Set TTLs based on how stale the data can be, not on arbitrary round numbers
- •Event-driven invalidation using CDC (Change Data Capture) from the database gives near-real-time cache consistency without coupling write paths to cache logic
- •Cache stampede happens when a popular key expires and hundreds of concurrent requests hit the database simultaneously. Use locking, probabilistic early expiration, or background refresh to prevent it
- •CDN cache purging has propagation delays of 1-30 seconds depending on the provider. Design your system to tolerate this window
Common Mistakes
- ✗Setting the same TTL for all cache keys regardless of access patterns and staleness tolerance. A user profile can be stale for 5 minutes; an account balance cannot be stale at all
- ✗Caching database query results without tracking which records contribute to that result. When a record changes, you cannot invalidate the right cache entries
- ✗Ignoring cache warming on deploy. A cold cache after deployment can overload the database during the first few minutes of traffic