Picking a Caching Approach Without Creating a Consistency Mess
Every caching decision is really a tradeoff between speed and staleness, and that tradeoff gets harder to reason about the moment a cache is distributed across multiple nodes or services. The approach that's fastest to implement, cache everything, invalidate on write, is also the one most likely to produce the subtle consistency bugs that show up as "why did this user see stale data" tickets weeks after launch.
Here's a comparison of the common approaches and where each one's tradeoffs actually bite.
Write-through: simple, consistent, and slower on writes
A write-through cache updates the cache and the underlying data store in the same operation, so reads are always consistent with the last write. The tradeoff is write latency, since every write now touches two systems instead of one, and if the two writes aren't wrapped in the same transaction, a partial failure can leave them out of sync. This approach fits read-heavy workloads where writes are relatively infrequent and consistency matters more than write speed, a product catalog, for example, that's updated occasionally but read constantly.
Write-behind: fast writes, real staleness risk
A write-behind (or write-back) cache updates the cache immediately and writes to the underlying store asynchronously, later. Writes are fast, but there's a real window where the cache and the source of truth disagree, and if the process crashes before the async write completes, that update can be lost entirely. This pattern fits high-write-volume scenarios where occasional data loss on the write path is an acceptable tradeoff, an analytics counter, for example, rather than a financial record.
What is cache-aside? Flexible, but invalidation is entirely on you
Cache-aside (or lazy loading) has the application check the cache first, and on a miss, read from the source of truth and populate the cache for next time. This is the most common pattern because it's simple to reason about for reads, but invalidation is entirely the application's responsibility: every code path that writes to the underlying data has to remember to invalidate or update the corresponding cache entry, and a missed invalidation path is the single most common source of stale-cache bugs in production.
This pattern tends to work well right up until a second write path is added, an admin tool, a batch import, a webhook handler from a partner, that nobody thought to wire into the same invalidation logic the primary application code already handles correctly. The original write path still works fine; it's always the new one that quietly serves stale reads for weeks before anyone notices the pattern.
How stale is acceptable? The decision that matters more than the pattern
Before picking an approach, answer a more basic question for each piece of cached data: if this is thirty seconds stale, does it matter? A product's inventory count during checkout has a very different staleness tolerance than a blog post's view counter. Segment your caching decisions by data type and its actual staleness tolerance, rather than applying one caching pattern uniformly across every kind of data in the system, since the right tradeoff for one dataset is often the wrong one for another.
- Match the caching pattern to each dataset's actual staleness tolerance, not a single default
- Prefer write-through for data where consistency matters more than write latency
- Reserve write-behind for high-volume data where occasional loss on write is genuinely acceptable
- Audit cache-aside invalidation paths specifically, since a missed one is the most common source of stale data
Write the staleness tolerance down next to each dataset's schema or service definition, not just in a design doc that gets forgotten soon after launch. A future engineer adding a new write path to that data has no real way to know the caching implications of their change unless that tolerance is documented somewhere they're actually likely to see it while working on the code.
For example, an online store might treat its inventory count during checkout as data that tolerates almost no staleness, so it uses write-through or skips the cache on the checkout path. The same store lets a blog post view counter run on write-behind, because losing a few counts is harmless. A useful decision rule: ask what a thirty-second-old value would cost you, and let the answer choose the pattern for each dataset separately.
What Good Looks Like
Every cached dataset has a deliberately chosen pattern matched to its staleness tolerance, cache-aside invalidation paths are audited for completeness, and a short TTL backstops anything where an invalidation could realistically be missed.
Building The Capability (5-Stage Skill Ladder)
How to Get Started
Frequently Asked Questions
How do we find where our current cache-aside invalidation is missing a path?
Trace every code path that writes to a piece of data the cache also serves, and confirm each one triggers an invalidation. A common gap is a bulk update or an admin-only write path that bypasses the normal application flow entirely and was never wired into the cache invalidation logic.
Is a short time-to-live (TTL) a safe substitute for explicit invalidation?
It's a reasonable safety net, not a substitute. A short TTL bounds how stale data can get even if an invalidation is missed, but relying on TTL alone for data that changes often means accepting that staleness as a default rather than an edge case, which may or may not be acceptable depending on the data.
Should we cache at the database layer, the application layer, or both?
It depends on what's actually slow. A database-layer cache (like a query cache) helps when the same expensive query runs repeatedly; an application-layer cache helps when you want to skip network calls to the database entirely for frequently accessed data. Profile before choosing, since adding a second caching layer you don't actually need just adds another place for staleness to hide.
About the numbers
This guide doesn't quote a sourced benchmark. Figures in it are estimates or general guidance, so check them against your own numbers.
Related Guides
Choosing a Caching Strategy Without Creating a Consistency Nightmare
A comparison of cache-aside, write-through, and write-behind caching, and how to decide which layer, CDN, application, or database, actually needs one.
Choosing a Caching Strategy Without Creating a Bigger Problem
Cache-aside versus write-through, when a local cache is enough, and why invalidation, not lookup speed, is the part of caching that actually breaks.
Choosing a Caching Strategy Without Overbuilding It
A decision guide for picking a caching approach that matches your actual read patterns, instead of defaulting to the most complex option available.
Cache Invalidation Is Still the Hard Part
A practical guide to choosing a caching layer and, more importantly, keeping it from serving stale or wrong data across a distributed system.
Caching Context So Your Agents Don't Pay for It Twice
Comparing where caching actually helps an agentic system, from prompt caching to tool result caching, and where it introduces stale-data risk instead.
Where Caching Helps a Zero Trust API and Where It Creates Risk
Comparing where caching genuinely speeds up a zero trust API against where it creates a real revocation and permission risk.