Skip to content
About
Main content

Read replicas and follower reads: the cost ladder

Which ways of reading buy freshness, and with which currency?

Adding a multiplies write work by one unit and divides nothing on the write side. So read replicas pay only when reads dominate. The break-even is explicit in the per-node work , a ratio you can compute instead of a default you adopt.

What makes the mechanism interesting is not capacity but the ladder of ways to read. Each rung buys freshness with a different currency:

MechanismExtra Depends onPays in
Read from the , naively0nothing, and it is unsafe, since a partitioned old leader still believes it leadscorrectness
ReadIndex (confirm leadership with one round, then serve locally)1 to a , no diskquorum reachabilitylatency
Leader 0a bound on clock drifta timing assumption in the argument
Quorum read (leaderless)1 to R nodesR + W > Nlatency on every read
Nearest , stale0nothingunbounded staleness
Follower read at a timestamp0 if caught up, else it waitsa closed/safe-timestamp signalstaleness converted into latency

The last rung is the interesting one. A follower that knows it has applied everything up to time T can serve any read at or before T locally and correctly, and it must wait for anything later. That converts an unbounded, invisible cost (staleness that the reader cannot detect) into a bounded, visible one (a wait that the reader can measure). It is an exchange of cost applied deliberately: staleness is traded for latency. The same move appears in verification, which can bill an RTT instead of a payload.

Note also the one rung where a touches safety. Everywhere else here, tuning a timeout costs availability only. A leader lease makes correctness depend on a clock-drift bound. This is why systems that use leases pair them with fencing on the write path, and why ReadIndex exists: it trades a round trip for the removal of the assumption.

The lag asymmetry

A leader applies writes using many cores. A follower historically replays them with one, because the log records an order and not a dependency graph. Under load, the apply path is narrower than the produce path, so lag does not stay constant: it grows, like a queue whose arrival rate is above its service rate. Little's Law (the number of items in a system equals arrival rate times time spent in the system) has no steady state there: the queue grows without limit. The usual fix is to enrich the log with enough dependency information to re-parallelise safely (MySQL's writeset-based parallel apply, Postgres's parallel-recovery work).

, and the follower is where you pay the second time. People rarely look for it there.

Compression: read scaling is free only if you pay staleness, and every mechanism that removes staleness reintroduces a round trip, a clock assumption, or a wait.