FjellSystem Design Lab
Lesson 4.3• 8 min read

Horizontal sharding and consistent hashing

Range vs hash partitioning, shard routers, cross-shard joins, two-phase commits, and distributed IDs.

What you will learn

  • Compare range, modulo-hash and consistent-hash sharding, including what moves when a shard is added.
  • Choose a shard key by cardinality, write spread and query co-location.
  • Handle queries without the shard key, transactions across shards, and IDs that are unique across shards.

Horizontal database sharding

Horizontal sharding partitions a single logical table across multiple independent physical database instances (shards) where each shard holds a mutually exclusive subset of rows. As with federation, each shard sees less traffic, has less to replicate, keeps more of its (smaller) data and indexes in memory, and accepts writes in parallel with the others. If one shard goes down the others keep working, but you still need replication within each shard to avoid losing its data.

In the ideal case, where load spreads evenly across shards:

Total capacity≈N×Capacityshard\text{Total capacity} \approx N \times \text{Capacity}_{\text{shard}}

In practice distribution can become lopsided: a set of power users on one shard can load it far more than the others.


1. Partitioning strategies

A. Range-based sharding

  • Mechanism: Rows are assigned to shards based on contiguous key ranges (e.g. Shard 1: user_id 1–1,000,000; Shard 2: user_id 1,000,001–2,000,000; or by creation dates).
  • Pros: Efficient range scans (WHERE created_at BETWEEN '2026-01-01' AND '2026-03-31').
  • Cons: High risk of hotspotting — all new writes hit the highest range shard while older shards sit idle.

B. Hash-based (modulo) sharding

  • Mechanism: Shard=hash(shard_key) mod N\text{Shard} = \text{hash}(\text{shard\_key}) \bmod N.
  • Pros: Uniform distribution of writes across all NN shards.
  • Cons: Resharding is expensive. Going from NN to N+1N+1 shards changes the result of  mod \bmod for about NN+1\frac{N}{N+1} of all keys (80% when going from 4 to 5 shards), forcing a cluster-wide data migration.

C. Consistent hashing with virtual nodes

  • Mechanism: Keys and shards are hashed onto the same circular hash space (a ring); each key belongs to the first shard found moving clockwise from it. The arrival or departure of a shard only affects its immediate neighbors on the ring.
  • Virtual nodes: Placing each shard at a single point spreads keys unevenly, so systems such as Amazon's Dynamo give each physical node many points ("virtual nodes"). This evens out the load and spreads a new node's intake across many existing nodes.
  • Rebalancing: Adding a shard moves only about 1N+1\frac{1}{N+1} of the keys (the new shard's fair share), and all of them move onto the new shard. Try it in the visual below.

2. Shard key selection criteria

Choosing the wrong shard key is the most expensive mistake in distributed database architecture:

  1. High Cardinality: The key must have millions of distinct values (e.g. user_id or account_id, never country_code or status).
  2. Uniform Write Distribution: Avoid monotonically increasing sequences as primary keys on range-sharded systems.
  3. Query Co-location: Queries should target a single shard via the shard key. If 90% of queries filter by account_id, sharding by account_id ensures O(1)O(1) single-shard routing.

3. Distributed joins and cross-shard queries

When a query cannot specify the shard key (e.g. SELECT * FROM orders WHERE status = 'PENDING'), the shard router must execute a Scatter-Gather (Fan-Out):

  1. Broadcast the query in parallel to all NN physical shards.
  2. Collect NN result streams.
  3. Merge, re-sort, and paginate the results in the router's memory.

A scatter-gather query is only as fast as its slowest shard (chapter 1's tail-latency effect): if each of NN shards is independently slow with probability pp,

P(the query is slow)=1−(1−p)NP(\text{the query is slow}) = 1 - (1-p)^N

which grows quickly with NN. Joining data across shards is likewise more complex, and the application logic must know how to work with shards.


4. Distributed transaction coordination

Pattern Mechanism Pros Cons
Two-phase commit (2PC) A coordinator asks every participant to prepare, and commits only if all vote yes; otherwise all abort. All participants commit or none do (atomic commit) Participants hold locks while they wait for the decision, and the protocol blocks if the coordinator fails
Saga A sequence of local transactions, coordinated by events (choreography) or by an orchestrator, with compensating transactions to undo completed steps if a later one fails. No distributed locks; each step commits on its own Compensations must be written by hand, and there is no isolation: concurrent sagas can see each other's partial work, so anomalies need countermeasures

5. Globally unique distributed ID generation

Auto-increment primary keys cannot be shared across independent database shards. Common alternatives:

  • Twitter Snowflake (64-bit integer): IDs are roughly time-ordered ("k-sorted"):
    41 bits⏟ms timestamp, custom epoch (69 years)+10 bits⏟machine id (1,024 machines)+12 bits⏟sequence (4,096 per ms per machine)\underbrace{41\text{ bits}}_{\text{ms timestamp, custom epoch (69 years)}} + \underbrace{10\text{ bits}}_{\text{machine id (1,024 machines)}} + \underbrace{12\text{ bits}}_{\text{sequence (4,096 per ms per machine)}}
    That is 63 bits, so every ID fits in a 64-bit integer.
  • UUIDv7 (128-bit, RFC 9562): A 48-bit Unix millisecond timestamp followed by random bits. Because successive values sort together, inserts land near each other in a B-tree index; random UUIDs (v4) scatter inserts across the index, which can hurt performance dramatically.

Sharding and routing a key

Shard routerRouting key: user_9482 ➔ Shard DB-2
Shard DB-1
64 vnodes · 23% of ring
2,500 write RPS1.2M rows
Shard DB-2
Target
64 vnodes · 23% of ring
2,500 write RPS1.3M rows
Shard DB-3
64 vnodes · 28% of ring
2,500 write RPS1.5M rows
Shard DB-4
64 vnodes · 26% of ring
2,500 write RPS1.6M rows
If you add shard 5 now: 20% of keys move to a different shard (measured on 2,000 sample keys; the minimum possible is about 20%, the new shard's fair share).
Strategy trade-off: Consistent hashing places each shard at many points ("virtual nodes") on a hash ring; a key belongs to the first shard clockwise from it. A new shard takes over only the arcs just before its own points, so only about 1/(N+1) of keys move, and only onto the new shard.
Figure 3: Horizontal sharding topology with Shard Router and distributed database partitions.
  • App Servers (60k RPS): 1 × Clients & Users
  • Shard Router (Vitess): 4 × Application Load Balancer
  • Shard 0 (Keys 00-3F): 2 × Primary Relational DB
  • Shard 1 (Keys 40-7F): 2 × Primary Relational DB
  • Shard 2 (Keys 80-BF): 2 × Primary Relational DB
  • Shard 3 (Keys C0-FF): 2 × Primary Relational DB
  • Redis Shard Key Cache: 2 × Redis In-Memory Cache
  • App Servers (60k RPS) sends requests to Shard Router (Vitess)
  • Shard Router (Vitess) sends requests to Shard 0 (Keys 00-3F)
  • Shard Router (Vitess) sends requests to Shard 1 (Keys 40-7F)
  • Shard Router (Vitess) sends requests to Shard 2 (Keys 80-BF)
  • Shard Router (Vitess) sends requests to Shard 3 (Keys C0-FF)
  • Shard Router (Vitess) sends requests to Redis Shard Key Cache

Key takeaways

  • Range sharding keeps range scans cheap but sends new writes to one hot shard; modulo hashing spreads writes but moves about NN+1\frac{N}{N+1} of the keys when a shard is added.
  • Consistent hashing with virtual nodes moves only the new shard's fair share, about 1N+1\frac{1}{N+1}.
  • A query without the shard key scatters to every shard and is as slow as the slowest: 1−(1−p)N1 - (1 - p)^N.
  • Cross-shard writes need two-phase commit (locks, blocking) or a saga (compensations, no isolation); IDs come from Snowflake or UUIDv7, not auto-increment.

Sources

Adapted from The System Design Primer by Donne Martin, licensed under CC BY 4.0. Changes were made.