Course outline · 0% complete

0/27 lessons0%

Course overview →

Read replicas: when one database is not enough

lesson 6-3 · ~9 min · 18/27

The read wall

Almost every web app reads far more than it writes: one user posts a comment, thousands load the page. Sooner or later the single database instance saturates, and the first instinct, renting a bigger one, has a doubling price at every step (lesson 3-1's catalog) and a hard ceiling at the top of the catalog. Engineers hit this wall at real companies constantly, and this lesson is the standard first answer.

A read replica is a second database instance that RDS keeps as a continuously-updated copy of the primary, fed by replication: every change to the primary is shipped to the replica and re-applied there. The replica gets its own endpoint and serves read queries only. All writes still go to the one primary, which is what keeps the data consistent.

Your app then splits its traffic: writes and must-be-fresh reads to the primary's endpoint, everything else (dashboards, search pages, reports) to the replicas. RDS supports several replicas per primary, and you can add them without downtime.

app 1,000 queries/s primary 100 q/s of 400 capacity read replica × 3 300 q/s each of 400 100 writes/s 900 reads/s async, lags by ms
Splitting traffic by kind rather than by volume. All 100 writes stay on the one primary, and the 900 reads spread across replicas that the primary feeds asynchronously.

The sizing arithmetic that motivates replicas

An app pushes 900 reads and 100 writes per second at a database instance that handles about 400 queries per second.

reads=900
writes=100
capacity=400
echo "one instance, $((reads + writes)) queries/s against $capacity capacity: overloaded"
replicas=$(( (reads + capacity - 1) / capacity ))
echo "fix: $replicas read replicas share the $reads reads/s"
echo "the primary keeps only the $writes writes/s: fine"

Output

one instance, 1000 queries/s against 400 capacity: overloaded
fix: 3 read replicas share the 900 reads/s
the primary keeps only the 100 writes/s: fine

The replica count uses (reads + capacity - 1) / capacity, which is integer division rounded up. 900 reads at 400 each needs 2.25 instances, and you cannot rent a quarter of one, so the answer is 3.

Note the shape of the fix: the writes were never the problem. 100 writes per second sits comfortably inside a 400 capacity, so the primary is fine once the reads move elsewhere. Recognizing which half of the traffic is actually saturating a database is the difference between adding replicas and paying for a much larger primary that does not help.

Lag, and replica versus standby

Replication to read replicas is asynchronous: the primary confirms a write to your app first and ships it to replicas a moment later. That gap is replication lag, usually well under a second, sometimes seconds under load. Design around it: any read that must reflect a write the same user just made goes to the primary.

Do not confuse a replica with the Multi-AZ standby from lesson 6-1, because they answer different questions:

Multi-AZ standbyread replica
exists foravailability (surviving an AZ failure)read scale
serves queries?no, idle until promotedyes, reads
replicationsynchronous (no lag)asynchronous (lag)

Production databases commonly run both. Replicas have one more career: a replica can be promoted to a standalone primary, and a replica in another region doubles as a disaster-recovery copy and a low-latency read point for far-away users (lesson 1-2).

Why a fresh write can read back stale

The cause is replication lag: the read hit the replica before the asynchronous copy of that write arrived.

Asynchronous replication means the replica is always slightly behind, usually by milliseconds, occasionally by more under load. The write is safe on the primary, and the replica is not broken. It simply had not applied that change yet when the page loaded.

The standard pattern follows directly. Route reads that must see the user's own fresh writes to the primary, and let replicas serve everything that tolerates a moment of staleness.

In practice this means classifying reads rather than routing them all one way. A profile page immediately after an edit goes to the primary, while a dashboard, a search page, or a report is happy on a replica and takes real load off the primary by being there.

What a dashboard should read from

The dashboard should read from a read replica.

The Multi-AZ standby is synchronous but invisible. It accepts no connections at all until a failover promotes it, so pointing anything at it is not a configuration you can even express. That is by design: a standby that served queries would have its own load profile and might not be ready when it is needed.

A read replica exposes its own endpoint precisely so you can point read-heavy workloads at it. Moving the dashboard's heavy SELECT queries there takes load off the primary without touching availability, and the two mechanisms keep working independently.

The confusion is common enough to be worth a rule: the standby is for surviving a failure, and the replica is for spreading reads. Paying for one does not give you the other.