Read replica or cache?
Both take read load off the primary database, and they are not interchangeable. One scales the number of queries you can serve; the other removes the query entirely. Picking wrong is how a design ends up with both.
When reads start to hurt, two answers arrive at the same time: add a read replica, or put a cache in front. They look like the same fix — less load on the primary — and they solve genuinely different problems.
What a replica actually buys
A read replica multiplies the number of queries the system can serve. Same engine, same schema, same query planner, same indexes: whatever the primary could answer, the replica can answer too. That makes it the right instrument when the problem is volume — many queries, each of them individually fine.
What it does not do is make a slow query fast. A report that takes four seconds on the primary takes four seconds on the replica. Adding replicas to fix latency buys the ability to run more slow queries at once, which is rarely what anyone wanted.
And it comes with replication lag: a read served by a replica can be milliseconds or seconds behind the write that just happened. For a feed, fine. For the screen a user sees right after saving a form, not fine — that is the read-after-write case, and it usually has to go to the primary regardless of cost.
What a cache actually buys
A cache removes the query. On a hit there is no database work at all, and the latency is whatever the cache costs — typically an order of magnitude less than the query it replaced. That makes it the right instrument when the problem is cost per read: an expensive computation whose result many callers want.
The price is two problems the database did not have. The first is invalidation: something has to decide when the stored answer stopped being true, and that decision lives in application code where it is easy to get wrong quietly. The second is what happens on a miss — a cache with a high hit rate is a statement that the thing behind it is sized for the misses only, which is its own failure mode.
- Many cheap queries, growing traffic → replica. The work is fine; there is just more of it than one machine can serve.
- One expensive result, many readers → cache. Doing it once beats doing it faster.
- Must reflect the write that just happened → neither. That read goes to the primary.
- Expensive query AND high volume → both, in that order: cache the result, and let the replica absorb what the cache misses.
The trap in the middle
The common mistake is reaching for a cache because the query is slow, without asking why it is slow. A missing index turns a 400 ms query into a 4 ms one and costs nothing to operate. A cache in front of that same query costs an invalidation strategy, a memory bill, a new failure mode, and a stale-data bug six months later — and it leaves the slow query in place for every miss.
Watching it on a diagram
The useful thing about deciding this on a canvas is that both candidate designs can exist side by side and run against the same workload. Draw the replica version, draw the cache version, and compare where each one saturates. The Cold cache preset is worth running on the cached design specifically, because it answers the question the hit rate hides: what the origin receives when the cache is contributing much less than it did in the happy path.