Cache Invalidation Strategies in Distributed Systems, Approached Practically
AI generated
60fps
ms
Web Performance · Caching · Distributed Systems · Backend
Cache invalidation strategies
in distributed systems, approached practically

Phil Karlton is credited with the line that there are only two hard things in computer science: cache invalidation and naming things. In distributed systems with multiple cache layers, CDN, application cache, and database cache, that problem gets noticeably more complex, because each layer brings its own invalidation logic and its own timing delay. Combining time-based and event-based invalidation with deliberate cache tagging makes the problem manageable, even though it never fully goes away.

16 min read Cache invalidation · TTL · Event-based Cache tagging · CDN · Distributed systems

1. The classic problem: only two hard things in computer science

The frequently quoted line that there are only two hard things in computer science, cache invalidation, naming things, and off-by-one errors, is more than an inside joke among developers. It describes a real, structural problem: a cache exists so expensive operations don't need to be repeated, but by definition it is a copy of data that is considered canonical somewhere else. The moment the original data changes, that copy must either be updated or marked invalid, and coordinating that turns out to be remarkably hard to get right in practice.

The difficulty is not in the idea itself but in the edge cases: how does the cache layer even know data has changed, if the change did not go through the exact same code path that populates the cache? What happens when multiple users change the same data at the same time? And how do you handle the window during which stale data gets served before invalidation actually takes effect? These questions have no universal answer; they require a deliberate choice of strategy that fits the consistency needs of the specific application.


Two fundamental strategies that combine well in practice:

1. Time-based invalidation (TTL)
   Cache entry expires after a fixed window, regardless of whether
   the underlying data actually changed.
   Simple to implement, but either too short (more load on origin)
   or too long (stale data visible for longer periods).

2. Event-based invalidation
   Cache entry gets explicitly deleted/updated the moment the
   underlying data changes (e.g. via a message queue event).
   More precise, but requires reliable event delivery across every
   system boundary -- this is exactly where the real complexity
   lives in distributed systems.

2. Time-based invalidation: TTL as the simplest approach

Time-based invalidation via a time-to-live (TTL) is the simplest and most widespread approach: every cache entry gets a fixed validity window, after which it automatically counts as invalid and gets reloaded from the original source on the next request. This approach requires no communication whatsoever between the data source and the cache layer, which makes it robust against network failures and simple to implement, even in heavily distributed systems with many independent services.

The downside lies in its fundamental imprecision: a TTL that is too short increases load on the origin system because data gets reloaded more often than actually necessary, while a TTL that is too long means users see stale information for extended periods, even though the underlying data changed long ago. In practice, TTL works especially well for data with predictable change frequency, such as product categories that change once a day, while it is often unsuitable for highly dynamic data like stock levels or prices.

3. Event-based invalidation: reacting to actual data changes

Event-based invalidation solves the precision problem of TTL by invalidating the cache not after a fixed time window, but exactly when the underlying data actually changes. As soon as a record gets updated in the database, the system publishes an event through a message queue like Kafka, RabbitMQ, or a simpler pub/sub system, which notifies every affected cache layer and either deletes the corresponding entry or overwrites it directly with the new data.

The decisive advantage is near-perfect theoretical consistency between data source and cache, because stale data is only visible during the short window between the data change and event processing. The price is increased system complexity: every code path that changes data has to reliably trigger an invalidation event, which in grown systems with many independent write paths, such as direct database migration scripts or batch jobs, is easily forgotten and leads to hard-to-find inconsistencies.


Typical event-based invalidation flow:

1. Client updates a product's price via the API
2. Application writes the new price to the database
3. Application publishes event: "product.updated" {id: 4711}
4. A cache invalidation service consumes the event
5. Service deletes/updates cache entries tagged
   "product:4711" in Redis, CDN, and application cache at once

Critical point: step 3 (publishing the event) and step 2
(the DB write) must be atomic, or at least reliably
recoverable (outbox pattern), otherwise cache entries end up
that never get invalidated.

4. Cache tagging for targeted partial invalidation

Without a structured link between data and cache entries, teams are left with two bad options: either flush the entire cache on every change, which momentarily creates massive load on the origin system, or manually and error-pronely track individual cache keys. Cache tagging resolves this dilemma by attaching one or more semantic tags to every cache entry, for example product:4711, category:electronics, or user:882, describing what content the cached entry actually depends on.

When a product changes, the system only needs to invalidate every cache entry tagged product:4711, regardless of how many different pages, API responses, or fragments reference that product in some form, say a product detail page, a category overview, and a search results page simultaneously. This targeted, partial invalidation drastically reduces the number of unnecessarily evicted cache entries compared to a full cache flush and keeps the cache hit ratio high even under frequent data changes.

5. Consistency problems across multiple cache layers

In real distributed systems, a single cache layer is rare. Typically a CDN sits between user and application, caching entire HTML pages or API responses at the network edge, with an application cache like Redis in front of or behind it for frequently read database values, and sometimes a query cache inside the database itself. Each of these layers has its own invalidation logic, its own TTL, and, crucially, its own latency when processing invalidation events.

This leads to a subtle but common consistency problem: the application cache gets invalidated correctly and instantly, while the CDN, which operates geographically distributed across multiple edge locations, only propagates the invalidation with a delay, since every individual edge node has to be notified separately. Users at different locations then see different versions of the same page for anywhere from seconds to minutes, which can become especially problematic for price-sensitive or legally relevant content such as stock availability.

6. CDN purge APIs and their limits in practice

CDN providers like Cloudflare, Fastly, or Akamai offer purge APIs that let applications invalidate individual URLs or, with more advanced providers, entire tag groups, similar in principle to application-level cache tagging. These APIs are usually fast, often propagating globally within seconds, but they come with technical and contractual limits: rate limits on the number of purge requests per time window, a maximum number of tags per request, and sometimes extra costs per purge operation at high frequency.

A common mistake in practice is triggering CDN purges synchronously on every single data change, which quickly hits the provider's rate limits on systems with high write frequency and, in the worst case, silently drops purge requests without the application ever knowing. A more robust pattern is batching invalidation events over a short window, say every five seconds, combined with deduplicating multiple changes to the same tags before actually sending the purge to the CDN.

7. Stale-while-revalidate as a pragmatic compromise

The HTTP Cache-Control directive stale-while-revalidate offers a pragmatic middle ground between strict consistency and maximum performance: when a cache entry has expired, the cache still serves the stale version to the user immediately, but triggers a background refresh from the origin system in parallel. The current user briefly still sees the old version but experiences no wait, while subsequent users already get the updated version from cache.

This pattern works especially well for content where brief inconsistency is acceptable, such as product descriptions, blog posts, or category overviews, but less well for content where freshness is business-critical, such as current prices in the checkout flow or availability indicators right before purchase. Combining stale-while-revalidate for non-critical content with event-based invalidation for critical data is, in practice, one of the most effective strategies.

8. Additional challenges in distributed systems

In distributed systems with multiple database replicas, another layer of complexity appears: eventual consistency between the primary database node and its read replicas means an invalidation event can fire before the data change has actually reached every replica. If the cache gets repopulated at that exact moment, it may still read the old version from a not-yet-updated replica, meaning the cache paradoxically ends up stale again despite correct invalidation logic.

A robust fix for this problem is to decouple the cache refresh from the invalidation event with a short, controlled delay matching typical replication lag, or, where available, to deliberately read from the primary database node instead of a replica when the request happens immediately after a known change. Read-your-write consistency patterns, which guarantee a user sees their own latest data right after their own change, solve this problem specifically for the most common and most important use case.

9. Conclusion: a combined strategy instead of a single silver bullet

Cache invalidation in distributed systems cannot be solved with a single universal strategy, because different data types have different consistency requirements. TTL-based invalidation suits rarely changing, non-critical data, event-based invalidation suits data where freshness matters, and cache tagging is what makes both approaches practical in the first place, since it enables targeted, partial invalidation instead of flushing the entire cache on every change.

The most important takeaway for teams working in distributed systems is to treat cache invalidation not as a one-time technical problem but as an ongoing trade-off between consistency, performance, and system complexity. Teams that deliberately choose the right invalidation strategy for each data category, while accounting for the added latency of multiple cache layers and eventual-consistency effects in distributed databases, get considerably closer to a workable solution to the old quote about the two hard things in computer science.

Strategy Consistency Implementation effort Well suited for
Time-based (TTL) low to medium low rarely changing, non-critical data
Event-based high high (message queue, outbox pattern) frequently changing, critical data
Cache tagging depends on combination medium partial invalidation with complex dependencies
Stale-while-revalidate briefly stale low non-critical, frequently read content
Read-your-write consistency high for the acting user high forms, checkout, personalized views

Mironsoft

Web performance, Core Web Vitals, and load time optimization

Load times that don't make users bounce before the page is even visible?

We review existing websites for slow Core Web Vitals, bloated JavaScript bundles, and unnecessary render blockers, then build a performance foundation that stays measurable instead of just looking good once.

Performance Audit

Systematically measuring and fixing Core Web Vitals, load waterfall, and render blockers.

Bundle Optimization

Specifically reducing JavaScript and CSS bundle size and improving code splitting.

Monitoring Setup

Establishing continuous performance monitoring instead of a one-time snapshot.

10. Summary

Cache invalidation in distributed systems at a glance

Core problem

A cache is a copy of canonical data whose freshness must be actively maintained.

Two base strategies

Time-based invalidation is simple, event-based invalidation is precise but complex.

Practical lever

Cache tagging enables targeted partial invalidation instead of a full cache flush.

Biggest pitfall

Multiple cache layers (CDN plus application cache) invalidate with different delays.

11. FAQ: Cache invalidation in distributed systems at a glance

1What does the quote about the two hard things in computer science mean?
It refers to cache invalidation and naming things as especially difficult problems in software development because they look simple on the surface but are actually full of edge cases.
2What is the difference between time-based and event-based invalidation?
Time-based invalidation lets cache entries expire automatically after a fixed window, regardless of actual data changes. Event-based invalidation deletes or updates cache entries precisely when the underlying data genuinely changes.
3What is cache tagging?
Cache tagging attaches semantic tags to every cache entry describing what content it depends on. When data changes, only entries with the affected tag need to be invalidated, instead of flushing the entire cache.
4Why do consistency problems arise with multiple cache layers?
Because each layer, such as CDN and application cache, has its own invalidation logic and its own latency when processing invalidation events. As a result, users can see different versions of the same page depending on location and timing.
5What is stale-while-revalidate?
An HTTP Cache-Control directive that immediately serves the stale version when a cache entry has expired while triggering a background refresh from the origin system, so the current user experiences no wait.
6How are eventual consistency and cache invalidation related?
With database replication, an invalidation event can fire before the change has reached every read replica. If the cache gets repopulated at that moment, it may still contain stale data from a not-yet-updated replica.
7What limits do CDN purge APIs have in practice?
CDN providers typically limit the number of purge requests per time window and the maximum number of tags per request. Triggering purges synchronously on every data change quickly hits rate limits under high write frequency.
8What is the outbox pattern in the context of cache invalidation?
The outbox pattern ensures an invalidation event gets published reliably even if publishing does not happen at the exact same moment as the database write, by first writing the event to an outbox table within the same transaction and delivering it asynchronously afterward.
9Should you always flush the entire cache on deployment?
Only in exceptional cases, such as structural changes to the data model. A full cache flush momentarily creates massive load on the origin system, while targeted, tag-based invalidation avoids that load.
10What is read-your-write consistency?
A consistency pattern that guarantees a user sees their own latest data right after making a change themselves, even if other users still see an older version due to cache or replication delay.