Library
Learn/Databases/Chapter 08
10 min read

Partitioning and sharding

The orders table is 4 TB. Every night a job deletes orders older than three years, and it now takes six hours, fights with daytime traffic, and leaves the table bloated. Queries for "this month's orders" read indexes that cover a decade. Nobody wants to shard. Nobody has to. Splitting this one table into monthly pieces, on the same server, fixes all three problems.

Partitioning and sharding both split data into pieces. Partitioning splits a table into pieces inside one database server. Sharding splits the data across several database servers. The first is a quiet afternoon's work. The second changes how your whole application works. This chapter is about knowing which you need.

Your answer

Partitioning and sharding both split data. Which one lets the database handle more writes per second, and why does the other one not?

Partitioning: one table, many pieces, one server#

Postgres and MySQL both let you declare a table as partitioned. To your queries it still looks like one table. Underneath it is several smaller tables, each holding one range of rows.

sql
CREATE TABLE orders (
  id          bigint,
  customer_id bigint,
  created_at  timestamptz NOT NULL,
  total       numeric
) PARTITION BY RANGE (created_at);
 
CREATE TABLE orders_2026_09 PARTITION OF orders
  FOR VALUES FROM ('2026-09-01') TO ('2026-10-01');
CREATE TABLE orders_2026_10 PARTITION OF orders
  FOR VALUES FROM ('2026-10-01') TO ('2026-11-01');

Three ways to split. Range, by date or id, the most common, for data that ages. List, by explicit values such as region or country. Hash, to spread rows evenly when there is no natural range.

Here is what a query does with it.

1/3 The query asks for this month. It does not need to know the table is partitioned.

The nightly delete becomes DROP TABLE orders_2023_09, which takes milliseconds instead of six hours and leaves no bloat behind. Old partitions can be moved to cheaper storage or archived. And because each partition has its own smaller indexes, maintenance like vacuum and reindexing happens in manageable pieces.

The catch: queries that do not filter on the partition key must check every partition, and in Postgres a unique constraint has to include the partition key. Partition by the column almost every query filters on.

Sharding: many servers#

Partitioning does not add capacity. Every partition still lives on the same machine, using the same CPU, memory and disk. When writes or data size outgrow the largest machine you can run, you shard: split the rows across several independent database servers, each a primary with its own replicas.

Figure 1. Each shard is a complete database with its own replicas. Something has to know which shard holds which rows, and the choice of shard key decides which queries stay on one shard.

Choosing the shard key and the way keys map to shards is its own chapter in the system design path: Sharding and Consistent hashing. The short version: pick a key with many values that nearly every query includes, such as customer_id or tenant_id, and never one that only increases, like a timestamp.

Who does the routing#

Routing
The app computes the shard from the key and connects directly. Simple and fast, and every service that touches the data needs the same logic, and every cross shard query is hand written code.

What gets hard#

Sharding solves capacity and hands you a new set of problems, permanently.

Queries without the shard key must ask every shard and merge the results, a scatter gather, which gets slower as shards are added.

Joins across shards are not possible in plain SQL. Keep data that is joined together on the same shard by giving it the same shard key: shard orders and order_items both by customer_id, and a customer's whole history lives together.

Transactions across shards need two phase commit or a saga, both slower and more complex than a local transaction. Design so that the common writes touch one shard.

Unique ids and constraints can no longer come from one auto increment sequence. You need a global id scheme, the subject of Design a unique ID generator.

Resharding, moving data when a shard fills up, has to happen while the app keeps running.

1/5 Shard 2 is too full. Half its customers will move to a new shard 4.
ChoiceWhat you gainWhat you payPick it when
Partition a tableFast pruning, instant archiving with DROP, smaller indexes, no application changes.No extra capacity, and queries without the partition key check every partition.Large tables with a natural range, usually time, on one healthy server.
Shard the databaseWrite capacity and storage that grow by adding servers.Cross shard queries, joins and transactions, global ids, and resharding forever.When one machine, with replicas and caching, can no longer take the writes or hold the data.
Partition, shard, or neither?
An events table grows 200 GB a month and is mostly queried for the last 7 days
A SaaS app where one database cannot take the write load of all tenants
A 300 GB database with slow queries and 50% CPU
A global social app, billions of rows, writes from every country
In the wild

Notion wrote in 2021 about sharding their Postgres database into hundreds of logical shards by workspace, after a single database could no longer keep up. GitHub has written about splitting its MySQL databases by feature and then using Vitess. Both posts spend most of their words on migration, verification and cutover, not on the sharding itself, which is a good guide to where the effort really goes.

Checkpoint#

Checkpoint

1. Which one increases how many writes per second the database can take?

2. A query on a table partitioned by month includes WHERE created_at >= last week. Why is it fast?

3. orders is sharded by customer_id. How should order_items be sharded?

Worth memorising
  • Partitioning splits a table on one server. Sharding splits data across servers.
  • Only sharding adds write capacity.
  • Range partition by time and drop old partitions instead of deleting rows.
  • Co-locate what you join: related tables share a shard key.
Say this in 60 seconds

Partitioning splits one table into pieces on the same server, usually by date range. It gives partition pruning, so queries on recent data skip old partitions, instant archiving by dropping a partition, and smaller indexes, without any application change, but it adds no capacity. Sharding splits data across several database servers by a shard key, and is the only way to add write capacity once one machine is full. Routing can live in the app, in a proxy like Vitess, or in a distributed SQL database. The cost is permanent: queries without the key scatter to every shard, cross shard joins and transactions are hard, ids must be globally unique, and resharding is a live migration. So I partition first and shard only when the lower rungs are exhausted, co-locating related tables on the same key.