Database Scaling: Replication, Partitioning and Sharding Explained
How to scale a database — vertical scaling, read replicas and replication lag, table partitioning, sharding strategies and shard keys, rebalancing, and alternatives to sharding.
Application servers are easy to scale: add more behind a load balancer. Databases are different. They hold state, must stay consistent, and can't simply be cloned. Scaling them is usually the hardest part of growing a system, so it pays to know the options and use them in the right order.
Key takeaways
- Optimize first: indexes, query fixes and caching are cheaper than any architectural change.
- Replication copies data to more nodes. It scales reads and improves availability, but introduces replication lag.
- Partitioning splits a large table into pieces within one database, which helps with manageability and query pruning.
- Sharding splits data across multiple database servers. It scales writes and storage, at significant complexity.
- The shard key is the most important sharding decision and is very hard to change later.
Step 0: Before scaling out
Most "we need to shard" problems are actually:
- Missing or wrong indexes (how database indexes work).
- N+1 queries and chatty ORMs.
- Repeated reads that should be cached (caching strategies).
- Connection exhaustion fixed by pooling (connection pooling explained).
- Analytics queries running on the production database. Move them to a warehouse.
- An undersized instance. Vertical scaling is often the cheapest next step (horizontal vs vertical scaling).
Replication
Replication maintains copies of data on multiple nodes.
Leader–follower (primary–replica)
textWrites ─▶ Primary ──(replication stream)──▶ Replica 1 ─▶ reads
└──────────────────────▶ Replica 2 ─▶ reads
- All writes go to the primary; replicas apply its change log.
- Reads can be distributed across replicas.
- If the primary fails, a replica is promoted (failover).
Synchronous vs asynchronous:
- Synchronous: the primary waits for a replica to confirm before acknowledging a write. No data loss on failover, but higher write latency, and writes stall if the replica is unavailable.
- Asynchronous: the primary acknowledges immediately. Faster, but replicas lag behind, and recent writes can be lost on failover.
Many setups combine them: one synchronous standby for durability, plus asynchronous read replicas.
Replication lag and its consequences
With async replicas, a user can update their profile and then read from a replica that hasn't received the change yet. Mitigations:
- Read-your-writes: route a user's reads to the primary for a short window after they write, or pin the session.
- Monotonic reads: stick a user to one replica so they never see data go "backward".
- Lag-aware routing: skip replicas whose lag exceeds a threshold.
- Keep critical reads (balances, permissions, checkout) on the primary.
These are consistency trade-offs in practice (CAP theorem explained).
Multi-leader and leaderless replication
- Multi-leader: several nodes accept writes, as in multi-region setups. It requires conflict resolution (last-write-wins, merge functions, CRDTs).
- Leaderless (Dynamo-style): clients write to and read from multiple replicas using quorums. High availability with tunable consistency.
Partitioning (within one database)
Split a huge table into smaller physical pieces while it stays one logical table:
sqlCREATE TABLE events (
id bigint, occurred_at timestamptz, user_id bigint, payload jsonb
) PARTITION BY RANGE (occurred_at);
CREATE TABLE events_2026_09 PARTITION OF events
FOR VALUES FROM ('2026-09-01') TO ('2026-10-01');
Benefits:
- Partition pruning: queries filtered by date scan only relevant partitions.
- Easy retention: drop old partitions instantly instead of running massive
DELETEs. - Smaller indexes per partition and more manageable maintenance.
Partitioning doesn't add servers. It improves efficiency on one. It's often the right move for large time-series or log-like tables.
Sharding (across databases)
Sharding distributes rows across multiple independent database servers, each holding a subset:
textApplication / router
├──▶ Shard 1: tenants A–F
├──▶ Shard 2: tenants G–M
├──▶ Shard 3: tenants N–S
└──▶ Shard 4: tenants T–Z
Sharding strategies
| Strategy | How | Pros | Cons |
|---|---|---|---|
| Range-based | Key ranges per shard | Efficient range queries | Hotspots (new data lands on one shard) |
| Hash-based | hash(key) mod N or consistent hashing | Even distribution | Range queries hit all shards; resharding moves data |
| Directory/lookup | A mapping table says where each key lives | Flexible placement, easy tenant moves | Lookup service is critical infrastructure |
| Geo-based | By region | Data residency, locality | Uneven sizes |
Consistent hashing (or many virtual shards mapped onto fewer physical nodes) reduces how much data moves when you add nodes.
Choosing a shard key
The shard key should:
- Be present in most queries, so each query hits one shard.
- Distribute load evenly. Avoid keys where one value dominates (a single giant customer).
- Keep related data together, such as all of a tenant's data on one shard, so transactions and joins stay local.
For B2B SaaS, tenant ID is the classic shard key. For consumer apps, user ID is common. Beware of "celebrity" or "whale" keys; you may need to isolate those on dedicated shards.
The costs of sharding
- Cross-shard queries (global reports, searches across users) become scatter-gather operations or move to a separate analytics store.
- Cross-shard transactions need distributed transactions or sagas (event-driven architecture).
- Unique constraints and IDs must be globally unique (UUIDs, Snowflake-style IDs).
- Rebalancing as shards grow, with data migration under live traffic.
- Operational load: backups, migrations and monitoring multiplied by the number of shards.
- Application complexity: routing logic, or a proxy layer.
Alternatives to hand-rolled sharding
- Distributed SQL databases that shard and rebalance automatically while providing SQL and transactions.
- Sharding extensions and proxies for existing databases.
- NoSQL stores with built-in partitioning when the access patterns fit (SQL vs NoSQL).
- Functional partitioning: move whole domains (events, notifications, audit logs) to separate databases before sharding any single table.
A pragmatic scaling path
- Optimize queries and indexes; add caching and pooling.
- Scale the primary vertically; move analytics off it.
- Add read replicas with lag-aware routing.
- Partition large tables by time or key.
- Split independent domains into separate databases.
- Shard, or adopt a distributed database, for the domain that still exceeds one primary.
This mirrors the broader progression in how to scale a web application.
FAQ
What's the difference between sharding and partitioning?
Partitioning usually means splitting a table into pieces within a single database instance. Sharding means distributing data across multiple database servers. Sharding is a form of horizontal partitioning across machines.
Do read replicas help with write-heavy workloads?
No. Every write still goes through the primary and must also be applied on each replica. Replicas scale reads. For write scaling, you need partitioning across nodes (sharding) or a distributed database.
When should I shard my database?
When a single primary can't handle your write throughput or data size even after optimization, vertical scaling, caching and moving non-core workloads elsewhere. For many applications, that day never comes.
How do I generate unique IDs across shards?
Use UUIDs (time-ordered variants like UUIDv7 are index-friendly), Snowflake-style IDs that combine timestamp, node ID and sequence, or ID ranges allocated per shard.