The Pattern
[primary: ALL writes + critical reads]
├──► replica-1 ── read traffic
├──► replica-2 ── read traffic
└──► replica-3 ── analytics, batch jobs
typical production ratio: 1 primary : 3-15 replicas.
each replica = full copy via async replication (own lesson).
the win condition: workloads are usually 90%+ reads —
replicas convert that entire mass into horizontal capacity.
Routing Reads Correctly
NOT all reads belong on replicas:
ROUTE TO REPLICAS (staleness-tolerant):
product pages, feeds, profiles, dashboards,
search, recommendations, reporting
KEEP ON PRIMARY:
writes' follow-up reads, auth-critical checks,
balance/stock verification at decision time,
anything inside a transaction
implementation: connection-per-purpose pools +
explicit routing hints in the data layer.
framework features (Django's db_for_read) formalize it.
Replica-Consistency Failures (learn these cold)
user updates profile → primary → replica lagging 800ms
immediate GET /profile → hits replica → OLD DATA SHOWN
variants that bite in production:
- update then redirect → detail page shows stale
- payment recorded → history list missing it
- password changed → old sessions still pass checks on replicas
every one is a support ticket titled
"I changed it but it's showing the old thing."
Read-Your-Writes Mitigations
1. STICKY WINDOW: after THIS user writes, pin their reads
to primary for N seconds (session flag). simple, effective.
2. LAG-AWARE ROUTING: monitor replication lag per replica;
route freshness-sensitive reads only to replicas within
threshold; fall back to primary when all lagging.
3. TOKEN/LSN CHECK: writes return position token;
reads wait until target replica confirms ≥ that position
(precise; supported in managed Postgres/MySQL tooling)
default recommendation: #1 for most apps, #3 where correctness matters.
Scaling Math and Limits
example: 40k reads/s, primary comfortable at ~8k
replicas needed ≈ ceil((40k - cached/primary share) ÷ per-replica)
≈ ceil(32k ÷ 10k) = 4 replicas (+1 spare)
HARD LIMITS of this pattern:
✗ WRITE throughput unchanged — all writes hit primary regardless
✗ replication lag grows WITH write volume (self-limiting)
✗ N replicas × WAL shipping = primary network/CPU tax
✗ connection storms: app fleet × replicas = thousands of conns
(poolers like pgbouncer become mandatory)
when writes saturate primary → sharding territory. replicas
bought you TIME and READ headroom; that was their job.
Interview Framing
“Database reads are slow at scale” scored shape: replica topology with count math, routing-by-consistency-tolerance table, read-your-writes mitigation named specifically (#1 or #3), pooler mention, then the honest ceiling sentence (“writes still funnel to primary — next rung is sharding”). That last sentence sets up the follow-up before it’s asked.
Premium Content
Unlock Read Replicas and all premium lessons with a subscription.
All premium lessons
Ad-free experience
Priority support
From ₹199.99/year — See plans