Engineering Atlas › System Design Fundamentals

Consistent hashing for stable key placement

13 min read · last reviewed 2026-10-17

Open in the interactive app: practice, quiz, mock interview →

In one minute

How it works

The problem with mod N

The obvious shard choice is hash(key) mod N: fast and even, until N changes. Going from 9 to 10 servers, a key stays put only when h mod 9 == h mod 10, about 1 key in 10. The other 90% move: a miss storm for a cache, or a 90% data copy for a store.

The ring

Consistent hashing hashes servers and keys into the same space, say 0 to 2^32 - 1, and treats that space as a circle. To find a key's owner, hash the key and walk clockwise until you hit a server's position. Each server owns the arc between its predecessor and itself.

Add a server and it takes over part of one arc; every other key keeps its owner. Remove one and its arc merges into its successor's. Movement is local by construction. Karger and colleagues introduced the idea in 1997 for web caches, defining the properties this lesson relies on: balance, monotonicity and spread (Karger et al.).

Virtual nodes

With one position per server the arcs are uneven: with 4 servers, one can own 40% of the ring and another 4%. When a server dies, its whole arc lands on one neighbour.

The fix is to place each server many times, as cache-3#0, cache-3#1, and so on. With 100 to 200 virtual nodes per server, each share is the sum of many small arcs and stays within roughly 10% of the mean. A failed server's arcs are scattered, so its load spreads across every survivor. Virtual nodes also give weighting: a machine with twice the memory gets twice the virtual nodes.

Replication on the ring

Dynamo, Cassandra and Riak also use the ring to choose replicas. A key's preference list is the first R distinct physical servers found walking clockwise; skipping virtual nodes of already-chosen servers (and zones) keeps two replicas off one machine. Dynamo's paper also reports that purely random tokens hurt in operation: bootstrapping a new node took almost a day in peak season, Merkle trees had to be rebuilt for changed ranges, and archiving was awkward. Amazon moved to equal-sized fixed partitions handed out to nodes, the same idea as logical shards (DeCandia et al.).

Other members of the family

Down the stack

Layer 1The contract

Given a membership set M and a key k, owner(k, M) is a pure function: every client holding the same M and the same hash function computes the same owner with no coordination. Monotonicity: if M' = M plus one server s, then for every key either owner(k, M') == owner(k, M) or owner(k, M') == s. Removal is symmetric. Expected movement on one change is K/N keys. Balance is probabilistic: with V virtual nodes per server, the largest share exceeds the mean by roughly a factor of 1 + O(sqrt(log N / V)). The contract says nothing about agreement. Two clients with different views of M will route the same key to different servers, and nothing in the algorithm detects that.

Layer 2The mechanism

A ring is a sorted array of (token, server) pairs. Lookup hashes the key and binary-searches for the first token greater than or equal to it, wrapping to index 0 past the end. Cassandra's Murmur3Partitioner uses signed 64-bit tokens from -2^63 to 2^63 - 1, and the num_tokens setting picks virtual nodes per node (256 in older releases, 16 by default since 4.0, paired with allocate_tokens_for_local_replication_factor: 3 so the allocator places tokens deliberately rather than randomly) (Cassandra NEWS). A node that joins with a different num_tokens from the rest of its datacenter owns a different share of data, so treat the value as fixed for the cluster's life. Membership travels by gossip, so ring views converge over seconds. Envoy's RING_HASH policy builds a ring of at least minimum_ring_size (1,024) and at most 8M entries, hashing with xxHash by default (Envoy docs). Memcached's libketama places servers 160 times each on average, taking four 32-bit chunks from each of 40 MD5 digests and scaling the count by each server's share of total memory (libketama).

Layer 3The machine

A ring with 50 servers and 160 virtual nodes each holds 8,000 entries. At 8 bytes of token plus an 8-byte pointer that is 128 KB, which fits in a typical 1 to 2 MB L2 cache. A binary search touches about 13 entries; when the ring is warm in cache that is well under 100 ns, and hashing a 40-byte key with xxHash or Murmur3 adds about 10 ns. Maglev's single array index is a few nanoseconds, which is why it fits packet-rate load balancers that handle millions of packets per second per core. The real cost is data movement: rebalancing 1/10 of a 20 TB cluster is 2 TB, about 27 minutes at a full 10 Gbps, and hours once streaming is throttled to protect foreground I/O.

Why it's built this way

  1. Networks and disks are the scarce resource, so the algorithm minimises the data that has to move when membership changes.
  2. Coordinating every routing decision through a central directory adds a round trip and a single point of failure, so ownership is computed locally from a shared hash function and membership list.
  3. Random placement of a few points is uneven, so each server gets many points, trading memory for balance.
  4. Big rings and per-lookup searches cost CPU at packet rates, so load balancers moved to fixed tables (Maglev) that trade a little extra movement for constant-time lookups.

Diagram

flowchart LR
  K["key: user:42"] --> H["hash32 = 0x9A31..."]
  H --> S["binary search sorted tokens"]
  S --> T["first token >= hash"]
  T --> W{"past the end?"}
  W -- "yes" --> Z["wrap to token 0"]
  W -- "no" --> V["vnode cache-3#17"]
  Z --> V
  V --> P["physical server cache-3"]
  P --> R["next distinct servers for replicas"]

Worked example

A product catalogue cache runs on 9 Memcached nodes at 200,000 reads per second with a 95% hit rate, so the database sees 10,000 reads per second and is sized for about 25,000.

The team adds a tenth node before Black Friday. With mod N, about 90% of keys change owner, the hit rate drops toward 10%, and the database sees up to 180,000 reads per second. It falls over, and the cache never warms because the warming requests time out.

With a ring of 160 virtual nodes per server, the new node takes about 1/10 of the keyspace from all nine existing nodes evenly. The hit rate dips from 95% to about 85% and recovers as the new node fills. The database sees roughly 30,000 reads per second at the peak of the dip. That still exceeds its budget, so the team adds the node in a quiet hour or pre-warms it by mirroring reads for its arc.

If one node dies at peak, its 1/9 share, spread over 160 arcs, falls to the other eight, each picking up about 1/72 of the keyspace. Without virtual nodes, one neighbour would absorb the whole 1/9 and double its load.

In the wild

Trade-offs

OptionStrengthsWeaknessesChoose when
hash mod NTrivial, perfectly even, O(1)Nearly all keys move when N changesN never changes, or a full reshuffle is cheap
Ring with virtual nodesNamed servers, weights, replicas by walking clockwiseMemory per vnode; balance only statisticalCaches and storage with arbitrary membership
Rendezvous hashingNo ring state, exact minimal movement, easy weightsO(N) hashes per lookupTens of servers, or choosing top-k replicas
Jump hashZero memory, very even, fastOnly add or remove the last bucketNumbered shards that grow append-only
Maglev tableO(1) lookup, small fixed tableSlightly more than minimal movement; rebuild on changeL4 load balancers at packet rate
Directory (lookup service)Any placement, moves one range at a timeExtra hop, a service to run and keep consistentRange scans, manual hot-spot splitting

Pitfalls in production

Leadership lens

Check yourself

  1. A cluster grows from 4 to 5 servers. Roughly what fraction of keys move under mod N, and under a ring?

    Show answer

    Under mod N, about 4/5 of keys move, because only keys where h mod 4 == h mod 5 stay. Under a ring, about 1/5 move, all of them to the new server.

  2. Why do virtual nodes help when a server fails, not just in steady state?

    Show answer

    The failed server's arcs are scattered around the ring, so each arc's keys go to a different successor. The extra load spreads over all survivors instead of doubling one neighbour.

  3. Two clients use the same ring code but route the same key to different servers. Name two likely causes.

    Show answer

    They hold different membership lists, for example one has not yet seen a join, or they use a hash that differs between processes, such as a per-process seeded string hash.

  4. When would jump hash be a poor choice?

    Show answer

    When servers are named and can fail in any order. Jump hash only supports removing the highest-numbered bucket, so losing bucket 3 of 10 forces an extra remapping layer.

Sources and further study

Primary

Systems docs

Books

Other curricula

Watch

Free resources

Open in the interactive app: practice, quiz, mock interview →