Read Replicas
Offloading read traffic to replicas; replication lag implications, read-your-writes consistency, replica sets, Aurora read replicas, routing reads to replicas, and replica promotion.
What it is
A read replica is a database instance that maintains a continuously updated copy of the primary (write) database by consuming the primary's replication stream (WAL in PostgreSQL, binlog in MySQL, redo log in Oracle). The replica accepts read-only queries, freeing the primary to focus exclusively on writes and critical reads. Applications route SELECT queries to replicas and INSERT/UPDATE/DELETE queries to the primary, effectively separating the read and write scaling planes.
Most cloud-managed database services support read replicas natively: AWS RDS and Aurora allow up to 15 replicas; Google Cloud SQL supports up to 10; Azure Database for PostgreSQL supports up to 5. Aurora takes this further with a shared-storage architecture where all replicas read from the same distributed storage layer, eliminating replication lag almost entirely and reducing replica creation time to seconds rather than hours for large databases.
Why it exists
Most OLTP databases have highly asymmetric read/write ratios — typically 80–95% of operations are reads. A single write primary is often underutilized in terms of read capacity while being CPU-saturated from index scans, sorts, and aggregate queries. Read replicas allow read throughput to be scaled independently of write throughput. Adding 4 replicas with identical specs effectively multiplies read capacity by 5× at the cost of only 4 extra instances — far cheaper than sharding the database or moving to a NoSQL alternative.
Replicas also serve a secondary purpose: disaster recovery. A replica in a different availability zone or region can be promoted to primary in minutes during a primary failure, providing a warm standby. This dual purpose — scaling and HA — makes read replicas one of the most common database architecture patterns in production systems.
When to use
- Your read/write ratio is heavily skewed toward reads (80%+) and the primary is CPU-bound from query execution.
- Reporting, analytics, or batch export queries are consuming primary resources and impacting OLTP performance.
- You want to provide read scale without the complexity of sharding — adding a replica is a single API call in most cloud platforms.
- You need a warm standby in a different availability zone or region for disaster recovery purposes.
- Your application can tolerate eventual consistency for reads (replication lag is acceptable for the use case).
- You want to run long-running analytical queries without acquiring locks that affect primary write performance.
When not to use
- Your workload is write-heavy; replicas only scale reads, not writes. The primary remains the bottleneck.
- Your application requires strict read-your-writes consistency (a user immediately reads back data they just wrote) and routing logic cannot enforce this.
- Replication lag for your use case is unacceptable (financial transactions, inventory counts where stale reads cause incorrect decisions).
- The primary is CPU-bound from writes, not reads; adding replicas will not help and sharding or batching writes is needed instead.
Typical architecture
Application
│
├─── Writes ──────────────────────►┌──────────────────┐
│ │ Primary (R/W) │
└─── Reads (non-critical) ────────►│ db-primary │
└────────┬─────────┘
Application also routes reads to: │ WAL / binlog
┌────────▼─────────┐
┌───────────────────────────────────┐ │ Replica #1 │
│ Read Load Balancer / Proxy │ │ db-replica-1 │
│ (RDS Proxy, ProxySQL, PgBouncer) │◄─┤ Lag: 0-200ms │
└───────────────────────────────────┘ └──────────────────┘
│ ┌──────────────────┐
├──── replica-1 ──────────────►│ Replica #2 │
├──── replica-2 ──────────────►│ db-replica-2 │
└──── replica-3 ──────────────►│ (cross-AZ) │
└──────────────────┘
Read-Your-Writes Pattern:
Write → primary
POST /comment
│
▼
token = primary.write(comment)
Return token with response header:
X-DB-Read-Token: lsn=0/3A2F8100
Next GET → replica (compare LSN):
If replica.lsn >= token → use replica
Else → fall back to primary
Aurora (shared storage):
Primary ──write──► Distributed Storage (6 copies)
Replica 1 ◄─read─┘ (no binlog lag, <10ms replica lag)
Replica 2 ◄─read─┘
Pros and cons
Pros
- Read throughput scales linearly with the number of replicas for read-heavy workloads.
- Simple to implement — most cloud databases support replica creation with a single API call.
- Reporting and analytical queries can run on dedicated replicas without impacting primary OLTP performance.
- Replicas in different AZs provide automatic failover candidates for disaster recovery.
- No application code changes are required beyond updating connection strings and routing logic.
Cons
- Does not scale writes — the primary remains the single write bottleneck.
- Replication lag introduces eventual consistency; stale reads are possible, which some use cases cannot tolerate.
- Routing logic must correctly segregate writes to primary and reads to replicas; mistakes cause data inconsistency.
- Each replica is a separate full copy of the database, which can be expensive for very large databases.
- Replica promotion during primary failure requires a brief write outage and may involve some data loss if replication was asynchronous.
Implementation notes
Replication lag monitoring is critical. Set up CloudWatch alarms (for RDS: ReplicaLag metric), Prometheus alerts, or database-level monitoring on replica_lag_seconds. A common threshold is >30 seconds, which indicates the replica is falling behind and reads from it may be significantly stale. During high write load (bulk imports, mass updates), replica lag can spike to minutes. Application code should be prepared to fall back to the primary for critical reads if replica lag exceeds a configurable threshold.
Read-your-writes consistency: After a user submits a form, they expect to see their submission reflected immediately. If the subsequent page load reads from a replica with lag, the user's own data may appear absent. The standard solution is session-level read routing: immediately after a write, route the user's subsequent reads to the primary for a brief window (e.g., 30 seconds), or use PostgreSQL LSN tracking to route reads to a replica only after it has caught up to at least the write LSN. Some ORMs (Sequelize, SQLAlchemy) and database proxies (RDS Proxy) support this natively. For non-critical reads (leaderboards, recommendations), replica staleness is typically acceptable.
Common failure modes
- Replica lag accumulation: A long-running transaction on the primary blocks the WAL stream, causing all replicas to lag simultaneously. Monitor transaction duration and set statement_timeout to prevent runaway queries.
- Read-after-write inconsistency: A user creates a record and immediately fetches it from a replica that hasn't received the replication event yet. Results in confusing "item not found" errors. Implement read-after-write routing as described above.
- Replica promotion data loss: The primary fails, and the replica promoted to primary is 5 seconds behind. Transactions committed in those 5 seconds are lost. Use synchronous replication for critical data (at the cost of higher write latency).
- Connection pool mismatch: Write connection pool is exhausted but read pool has spare capacity, or vice versa, because pool sizes are configured uniformly. Tune write and read pool sizes independently.
- Running DDL migrations against replicas: Schema migration tools that auto-discover and connect to all database hosts may run DDL on replicas, breaking replication. Ensure migration tooling only targets the primary.
Decision checklist
- Read/write ratio has been measured and confirms that reads are the dominant factor driving primary CPU load.
- Application routing logic correctly separates writes to primary and reads to replicas; no accidental writes to replicas.
- Replication lag monitoring and alerting is configured with acceptable thresholds defined per use case.
- Read-your-writes consistency is addressed for user-facing write-then-read sequences.
- Replica promotion procedure is documented and regularly tested as part of DR exercises.
- The number of replicas is appropriate for the read load; avoid over-provisioning replicas that increase replication stream overhead on the primary.
Example use cases
- SaaS dashboard queries: An analytics dashboard performs expensive aggregate queries that take 2–10 seconds each. Routing these to a dedicated read replica prevents them from competing with OLTP writes on the primary, improving both dashboard performance and primary write latency.
- Content platform homepage feeds: A publishing platform serves its homepage feed (a read-heavy query joining articles, tags, and authors) from a read replica, allowing the primary to focus exclusively on new article publications and comment writes.
- Data export jobs: A nightly export job that reads millions of rows is directed to a replica, preventing it from holding read locks and impacting primary performance during business hours.
Related patterns
- Caching Strategies — further offloads read pressure from replicas for frequently accessed data.
- Vertical Scaling — the complementary approach for write-heavy workloads where replicas don't help.
- Eventual Consistency — the consistency model that read replicas operate under; understand the tradeoffs.