PostgreSQLXIV · Replication, Slots and Read ReplicasReplication
Read replicas and what the application must accept
What you'll learn
- State the consistency guarantee a read replica actually offers
- Identify which queries can safely be routed to a replica
- Choose a routing mechanism and know what it cannot do
- Decide whether a read replica solves the problem you have
Prerequisites
Verified against PostgreSQL 18.x · PostgreSQL (comparison targets) 17.11, 16.15 · PostgreSQL (support calendar) 18, 17, 16, 15, 14 supported · pgBackRest 2.59.1 · PgBouncer 1.25.2 · Patroni 4.1.5 · Ubuntu (host baseline) 26.04 LTS · 2026-08-27
Read replicas are usually adopted to reduce load on the primary. What they actually do is move a consistency problem into the application.
The guarantee
A read replica gives you eventual consistency, with no bound on how
eventual. A query on the replica sees the database as of
pg_last_wal_replay_lsn(), which is some unspecified distance behind the
primary.
Lesson XIV-04 measured that distance as 2.5 ms at 1,623 tps on a healthy pair — and lesson XIV-07 measured the same pair 124 MB and 30 seconds behind, because one ordinary read query conflicted with replay. Both are normal operation.
The measurement that proves the hazard by failing to find it
$ for i in $(seq 1 200); do
psql -c "INSERT INTO rw(note) VALUES ('x')"
n=$(psql -h rbpg-sb -t -A -c "SELECT count(*) FROM rw")
m=$(psql -t -A -c "SELECT count(*) FROM rw")
[ "$n" != "$m" ] && stale=$((stale+1))
donetrials: 200 reads that did not yet see the write: 0What can safely go to a replica
Safe:
- Analytical queries and reports, where minutes-old data is fine.
- Dashboards and aggregates.
- Search across data that changes slowly.
- Exports and batch reads.
- Anything already tolerating a cache.
Not safe without care:
- Reading a record immediately after writing it.
- Any read-modify-write cycle.
- Uniqueness or existence checks before an insert.
- Anything a user perceives as “did my change save?”
- Session state.
The dividing line is not “reads versus writes”. It is whether the caller can distinguish stale data from correct data, and whether it matters when they can.
Routing
Application-level routing is explicit and it is the only mechanism that can express intent:
# unambiguous, and the decision is visible in the code
report_rows = replica.query("SELECT ... FROM big_table ...")
user = primary.query("SELECT * FROM users WHERE id = %s", uid)
target_session_attrs lets libpq choose from a list:
postgresql://host1,host2/app?target_session_attrs=read-write # primary
postgresql://host1,host2/app?target_session_attrs=prefer-standby
Useful for finding the primary after a failover, and not a substitute for deciding which queries may be stale.
A proxy (pgpool-II, HAProxy, pgbouncer with routing in front) can
route by rule. The rules are heuristics, and a heuristic that sends a
SELECT to a replica does not know whether that SELECT follows a
write in the same logical operation.
Does a read replica solve your problem?
Worth asking before building one, because the answer is often no.
If the primary is CPU-bound on reads — yes, this is what replicas are for.
If the primary is I/O-bound on reads — yes.
If the primary is write-bound — no. Every replica replays every write. Replicas add write load to the system as a whole and remove none from the primary.
If specific queries are slow — probably not. A slow query is slow on the replica too, and lesson X-08’s methodology will help more than a second machine.
If connection count is the problem — no. That is lesson IV-06’s pooling, and a replica multiplies connections rather than reducing them.
If you need a failover target — yes, and lesson XIV-07’s settings for that role are the opposite of a reporting replica’s. Part XV covers what else failover requires.
What to take from this
- A read replica is eventually consistent with no bound on how eventual.
- 0 stale reads in 200 trials on a healthy pair is why this bug reaches production, not evidence that it is safe.
- Route by whether the caller can tolerate stale data, not by read-versus-write.
- Read-your-writes needs the primary, or an explicit LSN wait. Never a sleep.
- A replica does not help a write-bound primary, a slow query, or a connection-count problem.
- The same query can be slower on the replica: no hint bits, a cache shaped by replay, and replay competing for I/O.
Cross-course references
- Kubernetes for Production Sysadmins — Part XXXVIII (Services) and Part XXXIX (Service discovery) cover routing reads to a replica set and what happens to that routing when a member is not caught up.
- Git, CI/CD & GitOps — Part CVI (Change management) covers getting the read-your-writes consequence agreed with the application team before the replica is offered to them.
Quiz
Knowledge check · 6 questions
Q1. A team tests read-your-writes against a replica 200 times and observes zero stale reads. What have they established?
Q2. A primary is saturated by write throughput. Two read replicas are added. What happens to the write load?
Q3. An identical query runs measurably slower on the standby than on the primary, with the same data and the same plan. What is the most likely reason?
Q4. Which workloads are safe to route to an asynchronous read replica without additional care? Select all that apply.
Q5. Inserting a short sleep between a write and a dependent read from a replica is an acceptable way to avoid stale reads.
Q6. What consistency guarantee does an asynchronous read replica offer, and how should an application decide what to route to it?
Passing score: 75%. Answers are checked in this browser.