Module 5 · Lesson 1 of 5
Read replicas
Copy the data, spread the reads, keep one place for writes.
Gatepass now runs ten API servers, and every one of them talks to the same single Postgres database. The API tier scales happily. The database sits at 95% CPU, queries queue, and the pages that browse events are slow for everyone. Most of that traffic only reads. That's the opening for read replicas.
The database is the next bottleneck
Stateless servers were easy to multiply because they held nothing. A database holds the truth, and you can't just start nine more and hope. Two copies that both accept writes can disagree about whether the last seat in row F is taken.
But look at the traffic. On Gatepass, people browse events, open ticket pages and check availability far more than they buy. A typical split is 90% reads and 10% writes. If the reads could go somewhere else, the main database would only need to deal with the writes.
Primary and replicas
The standard answer is single-leader replication:
- One primary (also called leader or master) accepts all writes.
- One or more replicas (followers) receive a stream of every change from the primary and apply it in the same order. They are read-only.
- The application sends writes to the primary and spreads reads across the replicas.
Technically, the primary writes every change to an ordered log (Postgres calls it the write-ahead log, MySQL the binary log). Replicas connect, read that log and replay it. Because they replay the same changes in the same order, they converge on the same data.
Try it
Start with 1,800 requests per second and no replicas. The primary is overloaded. Add replicas one at a time, then raise the write share and see what happens.
Interactive
Add read replicas
A write costs 2 units, a read costs 1, and each database node does 1000 units per second. Add replicas and watch which node runs out first.
Read replicas
Primary
all writes1,620 reads/s · 180 writes/s
Reads / writes
1,620 / 180
requests per second
Requests served
51%
the busiest node is saturated
This layout tops out near
909 req/s
add a replica to raise it
What to notice:
- With no replicas, one node does everything and is saturated.
- Each replica takes a share of the reads, so the primary's load falls, though not to zero. It still handles every write.
- Every replica also replays every write. At 10% writes that's a small cost. At 40% writes, replicas spend most of their time on writes that no reader asked for, and the benefit of adding another shrinks.
- If writes alone exceed what one node can do, replicas can't help at all. That's the wall, and the fix is to split the data across machines, covered in the sharding module.
What replicas are good for
- Read scaling. Browsing pages, search listings, ticket history. Anything that reads a lot and can tolerate data a moment old.
- Heavy queries. Point the nightly revenue report at a replica, so a 10-minute query does not slow down checkout.
- Failover candidates. A replica already has the data. If the primary dies, one becomes the new primary (the failover lesson).
- Geography. A replica in another region gives local readers a short round trip.
What they are not good for: raising write throughput, or serving reads that must see a write that happened a millisecond ago. Replication takes time, which is the next lesson.
Routing reads and writes
Somebody has to decide which queries go where. There are three common options:
- In the application. Two connection pools,
primaryandreplicas. Code picks explicitly:db.primary.insert(...),db.replica.select(...). Clear, and you can make exceptions. - In a library or ORM. Many frameworks support a read/write split and route
SELECTs to replicas automatically. Convenient, but aSELECTinside a transaction that also writes must go to the primary. - In a proxy. A middle layer parses queries and routes them. No code changes, but it is one more moving part.
Spread reads across replicas with round robin or least connections, and take a replica out of rotation when its health check fails, just like web servers.
Check yourself
Gatepass's database is at 95% CPU. 60% of the load is the nightly reconciliation job and ticket-list reads, 40% is checkout writes. What's the best first step?
Your turn
Which Gatepass queries would you send to a replica, and which must stay on the primary? Name at least two of each and say why.
Key takeaways
- A single database becomes the bottleneck once the app tier scales out. Most traffic is reads, which can move.
- Single-leader replication: one primary takes all writes, replicas replay its change log and serve reads.
- Replicas scale reads and heavy queries. They don't scale writes, since every replica repeats every write.
- Route reads and writes in code, a framework or a proxy, and take unhealthy replicas out of rotation.
- Replicas lag behind the primary and are not backups.
Finished reading?
Mark it complete to tick it off in the course.