TRT
Sign in
System Design

Shard Distribution with Consistent Hashing

How to split a keyspace across many machines so that adding or removing one server moves the minimum amount of data.

The Runtime Theory Team9 min read#sharding#consistent-hashing#partitioning#data-distribution

Sharding is the act of splitting a data set across multiple machines so that no single machine holds everything. The naive approach — assign shard 0 to server A, shard 1 to server B — breaks the moment a server is added or removed: every key's assignment changes, and everything must be resharded. Consistent hashing solves this by making the mapping of keys to servers independent of the server set.

The modulo problem

If you assign a key to server = servers[hash(key) % N], then adding one server changes N to N+1, and every key's modulo result changes. In practice, you rehash the entire dataset. For a database with 10 TB of data, that is a multi-day operation of copying data across the network. No production system does this.

How consistent hashing works

Place each server at a random position on a hash ring. To place a key, hash it, walk the ring clockwise, and assign it to the first server encountered. When a server is added to the ring, only the keys between it and the previous server on the ring move — the rest stay put. When a server is removed, only its keys are reassigned. The movement is bounded to 1/N of the key space, not 100%.

Virtual nodes: why one position per server is not enough

A single position per server means server placement on the ring is random — one server might get 30% of the ring, another gets 2%. To smooth this out, each physical server gets many virtual nodes (vnodes) — say, 150 positions on the ring. The keys are distributed more evenly, and when a server fails, its load is spread across many remaining servers instead of concentrating on one neighbor.

The shard key: what you hash matters

The shard key determines how data is distributed and how it can be queried. A good shard key distributes evenly across the hash space and is the primary lookup key. A bad shard key creates hot spots — all writes to the same user end up on the same shard, and that shard's server becomes the bottleneck.

The shard key also determines what queries are cheap and what queries are expensive. If you shard by user ID, finding a user by ID is a single shard hop. Finding all users in a city requires querying all shards — a scatter-gather query that is O(shards) in cost.

Where consistent hashing appears

Consistent hashing is not limited to databases. The load balancer article describes how a load balancer can use the same technique to route requests to backend instances — the request hash determines the backend, and adding or removing backends only reassigns the affected hash range. The rate limiter practice uses the same idea to distribute counters across multiple Redis instances.

The circuit breaker and bulkhead article explains how to handle the failure case — when a shard goes down, the circuit breaker prevents retry storms, and the bulkhead prevents the failure from cascading to other shards.

Production cost model and latency numbers

To apply Shard Distribution with Consistent Hashing effectively in production systems, engineers must move beyond qualitative diagrams and quantify the exact hardware and network costs at each boundary:

Operation / BoundaryTypical LatencyDominant BottleneckProduction Mitigation
L1 / L2 Cache Access1 ns – 4 nsRegister dependency chains & pointer chasingContiguous memory layouts, cache-line alignment (64 B), and batch iteration
Main Memory (DRAM) Fetch60 ns – 100 nsTLB misses, random heap indirection, NUMA hopsHugePages (2 MB), arena/slab allocators, and pre-allocated ring buffers
Kernel Syscall / Context Switch1 µs – 5 µsUser-to-kernel mode switch, page-table flush, lock contentionNon-blocking readiness loops (epoll / io_uring), batching, and zero-copy I/O
NVMe SSD Random Read / fsync50 µs – 500 µsBlock I/O queue depth, write amplification, WAL flushesGroup commit, sequential append logs, and bounded buffer-pool pinning
Intra-Region RPC Roundtrip0.5 ms – 2.5 msSerialization, TCP/TLS framing, tail queueing ($L = \lambda W$)Connection pooling, deadline propagation, and p95 hedged requests

Failure modes, debugging, and operational checklist

In real incidents, systems rarely fail at average (p50) load—they fail when a slow dependency, skewed hot key, or resource leak pushes concurrency across a non-linear saturation cliff. When reviewing code or architecture that relies on Shard Distribution with Consistent Hashing, walk through three verification questions:

  1. What is the invariant under partial failure or contention? If a worker crashes mid-operation, a network packet is delayed and reordered, or two threads race on the same state transition, prove which atomic primitive, version number, or idempotency key prevents silent corruption.
  2. How is backpressure enforced when demand exceeds capacity? Unbounded in-memory queues, unthrottled retries, and blocking calls without explicit deadlines turn a localized 100 ms slowdown into a fleet-wide Out-Of-Memory (OOM) cascade. Always bound queue depths, propagate caller deadlines, and shed excess load early.
  3. Which metrics and traces isolate the bottleneck in under 60 seconds? Instrument saturation, queue wait time vs service execution time, cache hit/miss ratios, and error rates by failure reason so on-call engineers can immediately distinguish a compute bottleneck from an I/O or lock-contention stall.

Spotted a mistake or something broken? Report a problem

Not started

Sign in to save your learning progress.

Sign in to save