Sharding vs Partitioning vs Replication
These three words get used interchangeably in architecture discussions, and they solve completely different problems. Getting them confused leads teams to shard when they had a read problem, which is an expensive way to make things worse.
The distinction
Replication — the same data, copied to more machines. Solves read capacity and availability. Every replica holds everything, so writes still go to one primary and the write ceiling is unchanged.
Partitioning — one table, split into pieces, inside one database. Solves unwieldy tables: enormous indexes, slow bulk deletes, vacuum pressure. The database still plans and executes queries as a single system, so joins, transactions and foreign keys all still work.
Sharding — data split across separate databases. Solves write capacity and dataset size. Nothing coordinates them by default, so the guarantees you took for granted stop applying.
The ladder matters: replication and partitioning keep you inside one database’s guarantees. Sharding leaves them.
Partitioning, which is usually enough
Postgres declarative partitioning splits by range, list or hash:
CREATE TABLE events (
id bigserial,
occurred_at timestamptz NOT NULL,
payload jsonb
) PARTITION BY RANGE (occurred_at);
CREATE TABLE events_2026_08 PARTITION OF events
FOR VALUES FROM ('2026-08-01') TO ('2026-09-01');
Two real wins:
Partition pruning. A query filtering on occurred_at touches only the
relevant partitions. Smaller indexes, less I/O.
Instant data lifecycle. DROP TABLE events_2026_02 removes a month in
milliseconds. The equivalent DELETE writes to the WAL, bloats the table and
leaves vacuum to clean up for hours.
The catch is that the benefit only exists when queries filter on the partition
key. Partition by month and then query by user_id and you scan every
partition — strictly worse than the unpartitioned table.
Sharding, and what it costs
Sharding is not a database feature you switch on. It is an architectural change that removes guarantees.
Cross-shard joins stop existing. Two tables on two machines cannot be joined by the database. You fetch from both and join in application code — no planner, no index-assisted merge, and a lot of manual work.
Cross-shard transactions stop being atomic. Writing to two shards is two independent transactions. One can commit while the other fails. Either you adopt two-phase commit, or the saga pattern, or you design so it never happens.
Unique constraints stop being global. The database can only enforce uniqueness within one shard. Globally unique values need a separate authority — which is one of the strongest arguments for UUIDv7 or ULID keys, since they need no coordination.
Rebalancing is a project. Adding shards means moving data while serving
traffic. Consistent hashing or a lookup table of key ranges reduces the pain;
modulo-based routing (hash(id) % shard_count) means adding a shard remaps
nearly everything, so avoid it from day one.
Choosing the shard key
This is the decision you cannot cheaply undo. Three requirements, and they pull against each other:
It appears in almost every query. Otherwise requests must fan out to all
shards and you have built a slower database. In multi-tenant systems tenant_id
is usually right — nearly every query is already scoped to one tenant.
It distributes evenly. Sharding by country puts your largest market on one
machine. Sharding by tenant works until one customer is fifty times bigger than
the rest, which is the normal shape of B2B revenue.
It rarely changes. Changing a row’s shard key means moving the row between databases.
Common good keys: tenant_id, customer_id, user_id. Common bad keys:
timestamps (all writes land on the newest shard), auto-increment ids (same),
low-cardinality categories.
Try these first, in this order
Sharding is the last step, not an early one. Most teams that shard did not need to:
- Index properly. A missing index can cost more than a machine. Start here.
- Fix N+1 queries. Often the entire “database is slow” problem. Here.
- Right-size the connection pool. More connections is usually slower. Here.
- Cache. Read pressure is what caches exist for.
- Read replicas. Reads are almost always the majority.
- Vertical scaling. A modern single instance handles far more than people assume, and the price of a bigger machine is trivially cheaper than a distributed-systems project.
- Partition the tables that have genuinely become unwieldy.
- Then consider sharding.
If your bottleneck is reads, sharding is the wrong tool entirely — you want replicas and caching, and sharding will add complexity without touching the constraint.
What you have actually chosen
Whether the database remains one system that can reason about all your data.
Replication and partitioning keep that property. Sharding trades it away for write throughput, and everything you lose — joins, atomic multi-row writes, global constraints — you rebuild by hand, in application code, forever.
Sometimes that trade is correct and unavoidable. It should never be the first thing tried, and it should never be reached for because it is what large companies do. They shard because they exhausted the list above. Most systems never get there.
Quick answers
- What is the difference between sharding and partitioning?
- Partitioning splits a table into pieces inside a single database, which still plans and executes queries as one system. Sharding splits data across separate independent databases, so the application or a proxy must decide where each query goes and cross-shard operations lose ACID guarantees.
- When should I shard my database?
- When write throughput or dataset size genuinely exceeds one machine and you have already exhausted indexing, caching, read replicas, connection pooling and vertical scaling. Sharding is the most expensive scaling step and the hardest to reverse.
- How do I choose a shard key?
- Pick the value that appears in the overwhelming majority of your queries — often tenant or customer id — so most requests hit one shard. It must also distribute evenly; a key that concentrates your largest customers on one shard recreates the bottleneck you were escaping.
- Does partitioning make queries faster?
- Only when the planner can eliminate partitions using the partition key, or when it lets you drop old data instantly instead of running a large DELETE. A query that does not filter on the partition key may get slower, because it must scan every partition.
References
Related Discoveries
Lumi's weekly note
A short email when we publish something useful. No spam, unsubscribe anytime.