Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

Read Replicas
HLD

Read Replicas

Offloading reads to copies — routing discipline, consistency handling, and the write ceiling replicas can't lift.

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.

My Private Notes

Notes are auto-saved locally to this device.