Reference Architecture: high scale / high traffic
As traffic grows, there is a right order for protecting the database: first the simple, cheap levers, then the complex ones. This tree walks step by step through the vertical scaling → read replica → sharding order, caching patterns (cache-aside, write-back), buffering with a queue + backpressure, rate limiting, isolating the database with bulkheads/circuit breakers, and preventing hot spots. Core principle: leave hard-to-reverse decisions such as sharding for last, and exhaust the cheap wins first.
Where is the bottleneck: is a single instance saturated, are reads or writes heavy, or is traffic bursty?
To solve a scaling problem in the right place, first identify the bottleneck. Is a single instance saturated on CPU/memory, are reads dominant, are writes hitting the limit, or are sudden traffic spikes crushing the database? Each points to a different component.
- A single instance is saturated; I have not tried the simple levers yet
- Reads are very heavy and growing
- Sudden traffic spikes / bursty load are crushing the DB
Which scaling step are you at: vertical, read replicas or sharding?
Scaling has a right order. Vertical scaling (more powerful hardware) is the simplest first step; then you spread reads across replicas; only when a single primary can no longer handle the writes do you move to horizontal partitioning (sharding). Where are you?
- I have not scaled up the hardware / exhausted the basic tuning yet
- Writes fit on one instance, but I need to spread the read load
- A single primary can no longer handle the write load
How will you protect the database from load: buffering, limiting or isolation?
Scaling is not always about making the DB bigger; often it is about protecting the DB from overload. Will you smooth sudden write spikes with a queue, limit the incoming request rate (rate limiting), or prevent a single dependency from bringing down the whole system (bulkhead/circuit breaker)?
- I want to smooth out sudden write spikes
- I want to limit/fairly share the incoming request rate
- I do not want one failing dependency to take down the whole system
Recommendation: vertical scaling and basic tuning first
The cheapest and least risky step is vertical scaling: adding more CPU/RAM/faster disks and fixing the basics (indexes, connection pooling, memory parameters). Exhaust these wins before complex decisions such as sharding.
Recommendation: spread reads with read replicas
Writes stay on a single primary while read-only traffic is spread across one or more replicas. It is much simpler than sharding and solves most read-heavy workloads; the primary remains only for writes and consistency-critical reads.
Recommendation: sharding (horizontal partitioning) as a last resort
When a single primary cannot handle the write load, you split the data by a key into multiple independent pieces (shards); each shard takes on part of the writes. It is powerful but hard to reverse; it complicates cross-shard queries and transactions, so it is left for last.
Recommendation: caching patterns (cache-aside + write-back)
With read-heavy high traffic, you put an in-memory cache (Redis) in front of the DB and serve most reads without them ever reaching the DB. For high-volume writes, write-back buffers writes and flushes them to the DB in batches, relieving the primary.
Recommendation: buffering with a queue (backpressure)
To keep sudden write spikes from hitting the DB directly, you put a durable queue (e.g. Kafka/a message queue) in between and consume writes at the rate the DB can sustain. The queue absorbs the spike; instead of failing synchronously, the system runs with controlled latency (backpressure).
Recommendation: rate limiting (limit the request rate)
By limiting incoming requests per source (rate limiting), you prevent both abuse and a single client consuming all resources. Capping the load that reaches the DB keeps the system predictable and fair.
Recommendation: bulkhead + circuit breaker (isolation)
To prevent a dependency (e.g. a slowing DB or an external service) from bringing down the whole system, you partition resources into compartments (bulkheads) and temporarily cut calls to a dependency that keeps failing (circuit breaker). This way a partial failure does not spread to the whole system.