← all posts

Caching Is a Consistency Decision

The moment you add a cache, you have a consistency problem.

"Add a cache" is common performance advice because it often works.

It can also introduce a distributed consistency problem into a system that used to have a simpler shape. The hard part is not usually Redis, Memcached, or the lookup path. The hard part is deciding what the system is allowed to know when the cache and the database disagree.

A cache is a second source of truth

The framing is wrong from the start. We talk about caching as a layer, something you slot between your application and your database to make reads faster.

The mental model is tidy: request comes in, check cache, miss, go to database, fill cache, return. That model is useful for explaining the read path, but it hides the part that matters operationally.

The moment you put a cache in front of a database, you have two systems that can answer the same question. If they ever disagree, you have a consistency bug. And they will disagree, because that's what distributed systems do.

This is distributed systems being mostly apologies with better marketing. The network will fail. Messages will arrive twice. Data will be stale by the time you read it.

A cache brings those problems into a system that used to have one source of truth.

Invalidation Is About Consistency

The famous quote is that there are only two hard problems in computer science: cache invalidation and naming things. It's funny because it's true, but the humor hides what's actually hard about it.

Cache invalidation is difficult because you have state in two places and need it to converge. The cache may not know when the database changes. The database may not know the cache exists. Once that relationship exists, you are doing distributed systems engineering whether the implementation looks simple or not.

The strategies people reach for are all consistency protocols with friendlier names. TTL is eventual consistency with a clock. Write-through buys stronger consistency with a performance tax. Write-behind accepts eventual consistency and adds durability risk. Cache-aside pushes consistency work into the application, which gives you more places to forget it.

Those choices define what stale means, how long it is tolerated, and who is responsible for repairing it.

Most teams don't make them consciously. They add Redis because reads are slow, set a TTL because that's what the tutorial showed, and discover six months later that they have a stale-data bug that only manifests under specific write patterns that nobody documented.

The Cache Changes the System

Once caching exists, it starts shaping the rest of the system.

Once you have a cache, every write path has to decide: do I invalidate, update, or ignore? Every read path has to decide: do I trust the cache, fall through, or warm it? Every failure path has to decide: what happens when the cache is down? Do you serve stale data, go to the database, or fail?

These decisions propagate. They show up in application code, deployment strategy, incident runbooks, and monitoring.

A cache becomes a participant in every read and write your system does, and it has consistency behavior that you have to reconcile with everything else.

That is the same lesson as observability being runtime architecture. Monitoring changes behavior because it is part of the runtime. Caching changes behavior because it is part of the consistency model.

When cache is the right architecture

Caching is often the right choice. Treating it as architectural work makes the real questions visible.

The better question is: am I designing a system with multiple sources of truth, and do I understand the consistency model that implies?

When you frame it that way, the decisions get clearer. Mostly immutable data can tolerate a long TTL because the staleness window is bounded and predictable. Fresh reads need a different answer, often a read replica with its own trade-offs. High-frequency writes with stale-tolerant reads can make write-behind caching reasonable, but that is also a durability decision.

The critical-path test is the one that usually matters most. If your cache goes down and the system cannot function, the cache is load-bearing infrastructure and should be operated that way.

That last point is where teams get hurt. They treat the cache as optional. Then it goes down, the database behind it cannot handle the load, and the system falls over.

The cache was never optional. It was load-bearing, and nobody operated it that way.

What to Design For

The practical checklist is straightforward: consistency model, failure mode, ownership, observability, recovery.

If those decisions are explicit, the cache can be a useful part of the architecture. If they are implicit, the cache will still make the decisions. It will just make them during an incident.

The one-line version

Caching improves reads by adding another source of truth, so design the consistency model before you add the cache.