
Scaling Relational Databases on AWS: Read Replicas and In-Memory Caching
Learn the standard architecture pattern for handling read-heavy database traffic using Amazon RDS Read Replicas and Amazon ElastiCache to improve throughput and reliability.
The Bottleneck of Growing Applications
As web applications grow, the single most common failure point is the primary relational database. Most consumer and enterprise platforms—from e-commerce catalogs to social feeds—run workload ratios of roughly 80% reads and 20% writes.
When all traffic hits a single database instance, CPU utilization spikes, connection pools exhaust, and API response times degrade. Rather than continually scaling up compute hardware (vertical scaling), real-world production systems rely on two foundational architectures: Read Replicas and Distributed Caching.
Architecture Overview
Instead of letting user-facing read traffic compete with transactional write queries, we decouple read and write operations:
- Write Path: The application routes create, update, and delete queries strictly to the Amazon RDS Primary instance.
- Read Path (Replica): Read queries for non-real-time data route to one or more Amazon RDS Read Replicas using asynchronous replication.
- Read Path (Cache): High-frequency queries check Amazon ElastiCache (Redis) first. If data exists, it returns immediately without querying RDS.
1. Offloading Reads with Amazon RDS Read Replicas
Amazon RDS uses the database engine's built-in replication (e.g., PostgreSQL streaming replication or MySQL binary logging) to maintain synchronized secondary instances.
Key Benefits
- Dedicated Compute for Reads: Heavy report generation and list endpoints do not block user checkouts or updates.
- Geographic Distribution: Cross-region read replicas lower query latency for users located far from the main application region.
- Failover Readiness: A read replica can be manually promoted to a standalone primary database if disaster recovery is needed.
Handling Replication Lag
Because replication is asynchronous, there is a minor delay (often sub-second under normal load, but higher during spikes) before writes reflect on replicas. For operations where consistency is non-negotiable—such as reading user balance immediately after a payment—always route the read query directly to the primary endpoint.
2. Eliminating Repetitive Queries with Amazon ElastiCache
Adding database replicas increases read capacity, but repeated network round-trips to SQL engines still cost latency. Putting an in-memory cache in front of RDS yields single-digit millisecond latency.
The Cache-Aside (Lazy Loading) Pattern
The application code manages cache validation through a three-step cycle:
- When a read request arrives, check ElastiCache using a unique key (e.g.,
user:1001:profile). - Cache Hit: Return the cached JSON payload directly.
- Cache Miss: Query the RDS read replica, write the result into ElastiCache with a Time-To-Live (TTL), and return the data.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import json
def get_user_profile(user_id, cache_client, db_client):
cache_key = f"user:{user_id}:profile"
# 1. Check ElastiCache
cached_data = cache_client.get(cache_key)
if cached_data:
return json.loads(cached_data)
# 2. On miss, query database replica
profile = db_client.query("SELECT * FROM users WHERE id = %s", (user_id,))
# 3. Store in cache with 10-minute expiration
cache_client.setex(cache_key, 600, json.dumps(profile))
return profileEnjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article