abhi-g.dev
DatabasesAug 20225 min read

Database sharding: when and how

Sharding removes cross-shard transactions, joins, and unique constraints, so it should be the last scaling step. What to exhaust first, how to pick the key, and how to leave room to reshard.

Originally written on 8 August 2022. Migrated from a WordPress blog and reformatted.

Sharding is horizontal partitioning across database instances: rows are distributed by a shard key, and each shard holds a disjoint subset. It scales writes and storage beyond a single machine. It also removes cross-shard transactions, cross-shard joins, and the ability to add an index and see it apply everywhere. It should be the last step.

Before sharding

Most databases that appear to need sharding need one of these first:

SymptomTry first
Read latency under loadread replicas, query optimisation, a cache
Table too large to index or vacuumpartitioning within one instance (PostgreSQL declarative partitioning)
One tenant dominatesmove that tenant to its own instance
Writes saturate one instancebatch writes, remove unnecessary indexes, faster storage
Storage limitarchive cold rows to cheaper storage

A single well-configured PostgreSQL instance on modern hardware handles tens of thousands of writes per second and several terabytes. Sharding below that scale adds cost without benefit.

Choosing the shard key

Every query that does not include the shard key must be sent to every shard. The key is therefore chosen by the access pattern, not by the data model.

  shard key: customer_id

  SELECT * FROM orders WHERE customer_id = 42          -> one shard
  SELECT * FROM orders WHERE id = 9001                 -> every shard
  SELECT * FROM orders WHERE created > '2022-08-01'    -> every shard
  SELECT count(*) FROM orders                          -> every shard, then sum

Good keys have three properties:

For multi-tenant systems the tenant id is almost always the answer. For consumer systems the user id usually is. Time-based keys are attractive for append-only data and disastrous for anything else, because all current writes land on one shard.

Mapping keys to shards

Hash. shard = hash(key) % N. Even distribution, no metadata, and every key moves when N changes. Acceptable when N is fixed for a long time.

Range. Contiguous key ranges per shard, held in a lookup table. Supports range queries on the key and rebalancing by splitting ranges. Requires the lookup table to be consulted on every query and risks hot ranges.

Directory. An explicit key -> shard mapping. Maximum flexibility, including moving one tenant at a time. The directory becomes a critical dependency and must be cached aggressively.

Consistent hashing sits between hash and directory: even distribution and minimal movement on resize, at the cost of some implementation complexity.

What sharding costs

Transactions span one shard. An order and its line items must share a shard key, or the write is no longer atomic. Design the schema so that every unit of work is co-located.

Joins across shards do not exist. Either denormalise the joined data into the row, or join in the application. Reference tables that are small and rarely change (countries, plans) are replicated to every shard.

Unique constraints apply per shard. A globally unique email address needs either a global lookup service or a scheme where the unique value determines the shard.

Schema migrations run N times, and a migration that succeeds on 9 shards and fails on the 10th leaves the system in a mixed state. Migrations must be backward compatible so that the application works during the rollout.

Operations multiply: backups, monitoring, failover, and capacity planning per shard.

Resharding

The number of shards will need to change. Plan for it at the start:

Summary

Shard when a single instance is at its limit after replicas, partitioning, caching, and tenant isolation have been exhausted. Pick the key from the query pattern. Co-locate everything a transaction touches. Over-provision logical shards from day one so that resharding is a data move rather than a rehash.