Invalidating a shared cache like Redis is one write. Invalidating an in-process, local cache that's independently duplicated across every one of 50 running application instances is a fundamentally different problem — there isn't one cache to write to, there are 50.
3 min read
A shared cache (Redis, Memcached) still costs a network round trip on every access — fast, but not free. An in-process cache (a plain dictionary or an in-memory LRU cache living inside the application process itself) avoids that round trip entirely for data accessed extremely frequently, at the cost of each application instance holding its own independent copy rather than one shared copy every instance reads.
The moment underlying data changes, the instance that made the change can update its own local cache — but every other instance in the fleet still holds the old value in its own local cache, with no way to know anything changed at all. A shared Redis cache has exactly one copy to invalidate; a fleet of 50 instances each running their own local cache has 50 copies, 49 of which just went silently stale the instant one instance's local cache was updated.
# The instance that made the change:
db.update_user(user_id, new_data)
local_cache.set(f"user:{user_id}", new_data)
redis.publish("cache-invalidation", f"user:{user_id}") # tell everyone else
# Every instance, including the one above, subscribes:
def on_invalidation_message(key):
local_cache.delete(key) # evict; next read on THIS instance repopulates from sourceRedis's own keyspace notifications (or a dedicated pub/sub topic, or — at larger scale — a real message queue, tying directly back to this domain's own message-queue coverage) broadcast "this key changed" to every subscriber. Each application instance subscribes and, on receiving a notification for a key, simply evicts that key from its own local cache — it doesn't need the new value pushed to it, just the signal that its current copy is now stale, so the next read on that instance naturally repopulates from the actual source of truth.
Between the moment instance A's write happens and the moment instances B and C actually process the invalidation message, there's a real window where B and C can still serve the stale value from their local caches — this is inherent to the asynchronous nature of pub/sub, not a bug in the implementation. For most use cases this window (typically milliseconds) is an acceptable trade for avoiding a network round trip on every single read; for data where even a brief staleness window is unacceptable, a shared cache (one copy, one invalidation, no propagation delay) or no caching at all is the more appropriate choice.
A shared Redis or Memcached cluster has exactly one logical copy of each key (ignoring its own internal replication, which is a separate durability concern) — invalidating it is a single operation, with no fleet-wide propagation step needed at all. This fleet-invalidation problem is specifically the cost local, in-process caching introduces in exchange for avoiding the network round trip — not a general caching problem every setup has to solve.
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. Why does invalidating a shared Redis cache differ fundamentally from invalidating local, in-process caches across a fleet?
2. How does pub/sub-based fleet invalidation actually work?
3. What real trade-off does pub/sub fleet invalidation accept?
Caching Strategies