Database Scaling Techniques: Match the Bottleneck
Checkout slows while product pages remain healthy. CPU is moderate, but the orders table shows rising lock waits and writes queue behind one hot index. Adding a read replica would not fix that incident because the bottleneck is on the write path.
This guide separates read load, write or storage limits, large-table scans, and repeated hot reads, then maps them to replicas, sharding, partitioning, or caching. User count and company size do not choose the technique; measurements on one representative request do. For machine-level scale-up versus scale-out, see Horizontal vs Vertical Scaling.
When one database is not enough
A single primary may own every read and write, but a slow application response does not prove that the database needs distribution. First follow one request through connection acquisition, query planning, lock waits, CPU, memory, storage I/O, and response serialization. A missing index or an unbounded query should be fixed before topology hides it.
Scale architecture only after the evidence names a capacity or availability limit. A larger instance may be the simplest safe step; replicas, partitions, caches, and shards each solve a narrower bottleneck and add routing, consistency, or operational costs.
Quick reference
- Request flow: API wait → connection pool wait → query execution → lock/I/O/CPU wait → rows returned → application work.
- Measure first: tail query time, connection saturation, locks, CPU, memory pressure, disk I/O, and rows scanned.
- Optimize query shape, indexes, and pool limits before adding distributed state.
- A larger machine buys headroom but does not remove its failure domain.
- Choose a technique only when its target bottleneck matches the trace.
Remember this
A missing index or an unbounded query fixed before adding topology is cheaper than the routing and consistency cost of a replica, shard, or partition that was never the actual bottleneck.
Read replicas: offload SELECT traffic
A read replica is a copy of the primary that serves reads. The app sends SELECTs to replicas and writes to the primary. Replication lag means replicas can be slightly behind — fine for product pages, risky for "read your write" right after checkout unless you route that path to primary.
When to use: read-heavy workloads (catalogs, feeds, reports). When to skip: write-dominated systems where the primary is already the bottleneck — replicas do not multiply write capacity.
Quick reference
- Primary = writes; replicas = reads.
- Watch replication lag for freshness-sensitive reads.
- Connection routing: read/write splitting in the app or proxy.
- A replica can support failover only with promotion, routing, and data-loss rules that you have tested.
- Failure path: if every replica is stale or unavailable, route freshness-critical reads to the primary or return a deliberate retryable error.
Remember this
Replication lag can return a stale "pending" status right after checkout unless that read-your-write path is explicitly routed to the primary — replicas scale reads, never write capacity.
Partitioning: split large tables inside one database
Partitioning divides a large table into pieces inside the same database — by range (date), list (region), or hash. Queries that prune partitions scan less data; old partitions can be archived or dropped cheaply.
Unlike sharding, you still have one logical database and usually one primary for writes. Partitioning is often the right move before sharding when a single huge table (events, orders by month) dominates I/O.
When to use: time-series or huge fact tables with predictable filters. When not to: small tables or random access by keys that never prune.
Quick reference
- Same DB, smaller physical pieces of a table.
- Partition pruning = fewer blocks scanned.
- Common: monthly order or event partitions.
- Sharding ≠ partitioning — shards are separate DBs; partitions are intra-DB.
- Verify with the query plan that expected partitions are pruned; a partitioned table can still scan every partition.
Remember this
A partitioned table can still scan every partition if the query filter doesn't prune — verify pruning in the query plan itself, since partitioning stays one logical database while sharding splits into many.
Caching: serve hot data from memory
Caching keeps reusable results in a faster layer so repeated product requests do not execute the same query. Flow: app → cache → on miss → database → attempt to populate → response. The useful result is measured origin work avoided without violating freshness, not a generic hit-rate target.
When to use: frequently read data with an explicit staleness allowance. When not to: write-sensitive balances, permissions, or personalized responses without safe keys and invalidation. See Caching 101 for layers and eviction; this section only places caching in the database-scaling decision.
Quick reference
- Cache hit = skip DB; miss = load and populate.
- TTL + invalidation keep freshness honest.
- Fails open: if cache is down, still read the DB.
- Pairs well with replicas — fewer reads reach any database.
Remember this
A cache outage should fail open straight to the database, not fail the request — the measured result that matters is origin work avoided without violating freshness, not a generic hit-rate target.
Choose the order that matches the bottleneck
There is no universal cache → replica → partition → shard ladder. Start with the wait you measured. Repeated identical reads suggest a cache; many independent reads saturating the primary suggest replicas; scans over one very large table suggest partitioning; write throughput or storage beyond one node may justify sharding. Sometimes an index, batch job, queue, or larger instance is the correct next move.
Run the decision on one checkout request: if it waits for a pool slot, reduce query duration or control concurrency; if it waits on a lock, shorten transactions or remove hot writes; if a read saturates the primary, test a replica with a freshness rule; if one tenant dominates writes, test shard-key distribution before splitting data.
Quick reference
- Repeated read work → cache only if the result is safely reusable.
- Read capacity or isolation → replica, with lag and fallback behavior defined.
- Large scans with a stable filter → partition and verify pruning in the plan.
- Write or storage ceiling → shard only after testing key distribution, routing, and rebalance.
- Lock or connection waits → fix transactions, queries, indexes, or admission control before distributing.
Remember this
There's no universal cache → replica → partition → shard ladder — the measured wait names the fix: a lock wait means shorter transactions, not a replica; a saturated primary means a replica, not a shard.
Key takeaway
Database scaling is bottleneck matching: replicas add read capacity, partitioning helps selected queries and lifecycle operations, sharding distributes data or writes, and caching avoids repeat origin work. None repairs a bad query automatically, and each needs an explicit stale, unavailable, or misrouted path.
Practice (25 min): Capture one slow request trace and its query plan, record the dominant wait, then choose one intervention with an expected measurable result such as fewer rows scanned, shorter pool wait, or lower primary read load under the same traffic. Intentionally disable that new path or force its stale/unavailable condition; recover through the fallback you documented. Pass when the target metric improves on the same workload and the broken path produces the planned fallback rather than silent wrong data.
Related Articles
Explore this topic