Consistent hashing ring: an interactive simulator
Consistent hashing places servers and keys on the same circle of hash values, and gives each key to the first server clockwise from it. When a server joins or leaves, only the keys on its arcs move, about 1 / N of them, instead of almost all of them as with hash(key) mod N. Each server sits at many points on the ring (virtual nodes) so the load comes out even.
The playground puts 300 keys and four servers on a ring. Add a server and compare the two counts: what moved on the ring, and what would have moved with mod N. Then try one point per server against a hundred.
Each dot is a key, coloured by the server it belongs to. Add a server and count what moves.
The mark on each bar is the fair share. Click a server's letter to remove it.
How consistent hashing works
- Hash every server onto the ring, many times each: server A at
hash("A#0"),hash("A#1"), and so on. Each position is a virtual node. - Hash every key onto the same ring:
hash("user:42"). - Walk clockwise from the key to the first server point. That server owns the key. In code this is a binary search over the sorted points, O(log n).
- A new server takes over the arc just before each of its points, from whichever server owned it. Nothing else moves.
- A server that leaves hands each of its arcs to the next point clockwise. With many virtual nodes, that is a little to every other server rather than everything to one neighbour.
Consistent hashing vs hash mod N
With hash(key) mod N a key's server depends on N, so changing N changes the answer for almost every key: going from N to N + 1 servers moves N / (N + 1) of the keys, 80% when a fifth server joins four. For a cache that is a near-total miss storm the moment you scale; for a database it is moving almost all the data. On the ring the same change moves about 1 / (N + 1) of the keys, 20%, and all of them to the new server, which is the minimum any scheme can move.
Mod N is still fine when N never changes, such as a fixed number of partitions you assign to servers separately. That is exactly what fixed-slot schemes do.
Why virtual nodes
With one point per server, the arcs between points are random lengths, so one server can own half the ring and another a twentieth; in the playground the busiest server holds around twice its fair share. Placing each server at 100 to 200 points splits its share into many small arcs, and the totals come out close to even, typically within 10 to 20%. Virtual nodes also make removal gentle: the leaving server's arcs are spread around the ring, so its keys go to many servers instead of doubling one neighbour's load. Give a bigger machine more points to give it more load.
Replication on the ring
To keep R copies, store each key on the first R distinct servers clockwise from it, skipping further points of a server already chosen. This is the preference list from Amazon’s Dynamo paper, and it means a server that fails has its keys already on its neighbours.
Which to use
| Scheme | Keys moved when a server joins | Lookup | State | Seen in |
|---|---|---|---|---|
| hash mod N | About N / (N + 1) | O(1) | None | Fixed partition counts |
| Hash ring with virtual nodes | About 1 / (N + 1), all to the new server | O(log of points) | The sorted points | Amazon’s Dynamo, Apache Cassandra, memcached clients (ketama) |
| Rendezvous (highest random weight) | About 1 / (N + 1) | O(N): score every server | The server list | Small clusters, where N is in the tens |
| Jump consistent hash | About 1 / (N + 1) | O(log N), no memory | None | Numbered shards that only grow or shrink at the end |
| Fixed slots | Only the slots you choose to move | O(1) | A slot table | Redis Cluster: 16,384 slots |
Consistent hashing in code
The whole ring is a sorted list of points and a binary search:
import bisect, hashlib
def h(s: str) -> int:
return int.from_bytes(hashlib.md5(s.encode()).digest()[:8], "big")
class Ring:
def __init__(self, servers, points_per_server=100):
# every server at many places on the ring: its virtual nodes
self.points = sorted((h(f"{s}#{i}"), s) for s in servers for i in range(points_per_server))
self.hashes = [p for p, _ in self.points]
def server_for(self, key: str) -> str:
# the first point clockwise from the key, wrapping past the top
i = bisect.bisect_left(self.hashes, h(key)) % len(self.points)
return self.points[i][1]Any well-spread hash works; it does not need to be cryptographic. Rebuild the list when servers change, or keep it in a sorted map and insert and delete a server’s points.
In a system design interview
Say it with the numbers: “Keys are spread over cache nodes with consistent hashing, about 150 virtual nodes each so load stays close to even and a lost node’s keys spread over all the others; each key is stored on the next two distinct nodes clockwise.” Expect the follow-ups: hot keys (consistent hashing spreads keys, not traffic, so a celebrity key needs its own fix, such as replicas or a local cache), moving the data when a node joins, and bounded loads, which cap any server at a little over the average.
Watch a strong candidate design a distributed cache, or read the consistent hashing guide for jump hash, rendezvous hashing and hot keys in depth.
Questions people ask
What is consistent hashing?
A way to spread keys over servers so that adding or removing a server moves only a small share of the keys. Servers and keys are hashed onto the same circle of values, and each key belongs to the first server clockwise from it. A server joining takes about 1 / N of the keys; with hash(key) mod N almost all of them would move.
What is the difference between consistent hashing and hash mod N?
With hash mod N a key’s server depends on the number of servers, so going from N to N + 1 servers moves N / (N + 1) of the keys: 80% when a fifth server joins four. With consistent hashing the same change moves about 1 / (N + 1), 20%, all of it to the new server.
What are virtual nodes in consistent hashing?
Extra positions for each server on the ring. With one position per server the arcs between servers are uneven and one server can get twice its share. Placing each server at 100 to 200 positions brings the load close to even, typically within 10 to 20%, and a server that leaves hands its keys to many servers instead of one neighbour.
How many keys move when a server is added with consistent hashing?
About 1 / (N + 1) of them, where N is the number of servers before the change, and every moved key goes to the new server. That is the minimum any scheme can move while keeping the load even.
How does replication work with consistent hashing?
Each key is stored on the first R distinct servers clockwise from it, skipping further positions of a server already chosen. This preference list, from Amazon’s Dynamo paper, means a failed server’s keys already have copies on its neighbours.
Does consistent hashing fix hot keys?
No. It spreads keys evenly, not traffic: a single key that gets most of the requests still lands on one server. Hot keys need their own fix, such as replicating that key to several servers, adding a local cache in front, or splitting the key with a suffix.
What is the difference between consistent hashing and rendezvous hashing?
Rendezvous hashing scores every server for each key with hash(key, server) and picks the highest score. It moves the same small share of keys as a ring and needs no virtual nodes, but each lookup costs O(N) instead of O(log N), so it suits clusters of tens of servers rather than thousands.