Part 4 · Databases & Data at Scale
Consistent Hashing, Visually Explained
Add a fifth cache server to hash(key) % 4 and 80% of your keys move — the hit ratio collapses and the database falls over. How the hash ring moves only ~1/N of keys, why virtual nodes even out the load, and the alternatives.
On this page
The Problem
You have 4 cache servers. A cache is a fast store that keeps copies of data. You spread the keys between the servers with a simple rule. A hash is a number that a function makes from a key. The % sign means "remainder after division":
server = hash(key) % 4It works nicely. Then traffic grows and you add a 5th server. The rule becomes hash(key) % 5. Suddenly about 80% of the keys map to a different server. Your cache hit ratio (the share of requests that find their data in the cache) drops from 95% to almost zero. Every request goes to the database, and the database cannot cope and fails.
The same thing happens when a server crashes. The number changes from 4 to 3, and most keys move again.
Consistent hashing fixes this. It is a way to choose a server for each key so that, when a server is added or removed, only a small part of the keys move. This is roughly 1/N of them, where N is the number of servers.
The Core Idea
Imagine a round table with seats numbered from 0 to 99, like the numbers on a clock face.
- A few waiters (the servers) stand at certain seats: waiter A at seat 10, B at seat 45, C at seat 80.
- Every guest (a key) gets a seat number from a hash function.
- Each guest is served by the first waiter you meet when you walk clockwise from the guest's seat.
Now a new waiter D comes and stands at seat 60. Only the guests between seats 45 and 60 switch to D. Everyone else keeps their waiter.
If waiter B leaves, only B's guests move. They go to the next waiter clockwise. Nobody else is affected.
This round table is the hash ring.
How It Works
Why modulo hashing breaks
With hash(key) % N, the server for each key depends on N (the number of servers). If N changes, the answer changes for most keys. Here is an example, using small numbers as the hashes:
| key hash | % 4 | % 5 | Moved? |
|---|---|---|---|
| 10 | 2 | 0 | ✅ moved |
| 11 | 3 | 1 | ✅ moved |
| 12 | 0 | 2 | ✅ moved |
| 20 | 0 | 0 | stayed |
| 21 | 1 | 1 | stayed |
| 22 | 2 | 2 | stayed |
| 23 | 3 | 3 | stayed |
| 24 | 0 | 4 | ✅ moved |
| 25 | 1 | 0 | ✅ moved |
In this small sample, 5 of 9 keys moved. Across all keys, going from 4 to 5 servers keeps only about 1 in 5 keys in place, so about 80% move. Going from 100 to 101 servers moves about 99%. For a cache, this means a flood of misses (the data is not found in the cache). For a database, it means moving nearly all the data.
The hash ring
- Imagine all possible outputs of a hash function (say 0 to 2³² − 1) arranged in a circle. After the biggest number, you wrap around to 0.
- Hash each server (for example, its name or IP address) to get a point on the ring.
- Hash each key to get a point on the ring.
- A key belongs to the first server clockwise from its position.
To keep it simple, use a small ring from 0 to 99, with servers at A = 10, B = 45, C = 80. Picture this line bent into a circle, so that position 99 joins back to 0:
White-ringed keys moved in the last change. With % N almost every key moves; on the ring only the keys next to the changed server do. Virtual nodes even out the load bars.
| Key | Position | Walk clockwise to | Owner |
|---|---|---|---|
| k1 | 5 | 10 | A |
| k2 | 30 | 45 | B |
| k3 | 50 | 80 | C |
| k4 | 70 | 80 | C |
| k5 | 90 | goes past 99, wraps to 0, then reaches 10 | A |
Adding a server
Add D at position 60:
- B at 45
- D at 60takes keys 46–60 from C
- C at 80keeps keys 61–80
Only the keys between 45 and 60 (the arc just before D) move, from C to D. Everything that A and B own is untouched. So are C's other keys.
Removing a server
Now suppose B (45) dies. Its keys (the arc from 10 to 45, including k2) move to the next server clockwise, which is C. A's keys are not affected.
On average, adding or removing one server moves only about 1/N of the keys. With 5 servers, that is about 20%. With modulo hashing it is about 80%.
The problem with a simple ring: uneven load
A server's arc is the part of the ring that it owns. If you have only a few servers at random points, the arcs can be very uneven:
Also, when a server fails, all of its load goes to one neighbour. That neighbour might then fail too. This chain of failures is called a cascade.
Virtual nodes (vnodes)
The fix is to place each physical server at many points on the ring, often 100–200. Each point is a virtual node (vnode). You make the points with different hashes, like hash("A#1"), hash("A#2") and so on.
Ring positions: A1 C1 B1 A2 B2 C2 A3 C3 B3 A4 ... (each physical server appears many times, spread out)- ✅ Even distribution. With many points, each server's total share becomes close to 1/N.
- ✅ Failures spread out. When A dies, its many small arcs go to many different servers, not just to one neighbour.
- ✅ Mixed hardware. A server that is twice as powerful gets twice as many virtual nodes, so it holds twice the data.
- ❌ More metadata (extra information) to track, because the ring map is bigger. This is a small cost.
Replication on the ring
Distributed databases also use the ring for replication (keeping copies). Store each key on the owner and on the next N−1 distinct physical servers clockwise. Here N is the number of copies you want:
Replication factor 3: key → first server clockwise (primary) + next 2 distinct servers (replicas)This is how Dynamo-style databases (Cassandra, Riak, ScyllaDB) decide which nodes hold the copies of each piece of data. It connects with the quorums in Lesson 24. Real systems also make sure that the replicas are on servers in different racks or availability zones (separate data centre areas, so one failure does not hit all copies).
Looking up a key efficiently
In code, the ring is usually a sorted list of node positions. To find the owner of a key, you binary search for the first position after the key's hash. Binary search checks the middle of the list and keeps only the half that can hold the answer. If there is no position after the hash, you wrap around to the start of the list. This takes O(log M) steps, where M is the number of virtual nodes. So it is very fast.
import bisect, hashlibdef h(s): # hash to a big integer return int(hashlib.md5(s.encode()).hexdigest(), 16)class Ring: def __init__(self, nodes, vnodes=100): self.ring = sorted((h(f"{n}#{i}"), n) for n in nodes for i in range(vnodes)) self.keys = [pos for pos, _ in self.ring] def owner(self, key): i = bisect.bisect(self.keys, h(key)) % len(self.ring) # wrap around return self.ring[i][1]ring = Ring(["cache-a", "cache-b", "cache-c"])print(ring.owner("user:42")) # cache-a(MD5 is fine here, because we only need an even spread of numbers, not security.)
Alternatives worth knowing
- Rendezvous hashing (Highest Random Weight, HRW). For each key, compute a score for every server (
hash(key + server)) and pick the server with the highest score. It is simple and gives an even spread, with no ring or virtual nodes. But one lookup takes O(N) steps. This is fine for tens or hundreds of servers. - Jump consistent hash (from Google). A tiny and fast algorithm. It maps keys to numbered buckets 0…N−1, with little movement and an almost perfect balance. The catch: the servers are numbered, so you can only add or remove servers at the end of the list.
- Maglev hashing (from Google). It uses a lookup table to route traffic in a load balancer (Lesson 12) very fast and evenly.
- Consistent hashing with bounded loads. This adds a limit, so that no server gets more than, say, 125% of the average load. The extra load goes to the next server. It protects against hot spots.
Trade-offs
- ✅ Very little data moves when servers join or leave (about 1/N). This is essential for caches and for elastic databases (databases that grow and shrink as needed).
- ✅ With virtual nodes, you get even load and smooth handling of failures.
- ❌ It does not solve hot keys. One very popular key still lands on one server. Use replication or caching for those keys (Lesson 16).
- ❌ More complex than modulo. Every client or router needs the same view of the ring. The list of members must be kept in sync, for example with gossip (nodes tell each other the news) or with a coordination service.
- ❌ Range queries are scattered across servers, like in any hash-based partitioning (Lesson 25).
When not to bother: you have a fixed number of servers that never changes. Or a full reshuffle is cheap, for example a small cache that is easy to rebuild.
In the Real World
Akamai and the original paper. Consistent hashing was introduced in a 1997 paper by David Karger and colleagues at MIT. It was made to spread web content across many caching servers. Several of the authors helped to found Akamai, one of the first and largest CDNs (networks of servers that deliver web content). Akamai used these ideas in practice.
Amazon Dynamo, Cassandra and ScyllaDB. Dynamo used a consistent hash ring with virtual nodes to spread and copy data. Cassandra (originally built at Facebook) and ScyllaDB use the same ring-and-tokens model. When you add a node to a cluster, only a part of the data moves to it.
Memcached clients. A popular consistent-hashing method for Memcached, called Ketama, was published by Last.fm. Many Memcached client libraries support it. So adding or removing cache servers does not wipe out most of the cache.
Discord. Discord uses a consistent hash ring to send each server ("guild") to one particular process in its cluster. It has open-sourced its hash-ring library for Elixir (a programming language). When nodes are added or removed, only some guilds move.
Vimeo and bounded loads. Vimeo engineers added "consistent hashing with bounded loads" to the HAProxy load balancer. It makes caches work better for video delivery, and it stops any single server from being overloaded. The idea came from a Google research paper.
Interview Questions
Q1Why is hash(key) % N a problem for caches?
The server for a key depends on N. So if N changes (you add a node or lose one), almost every key gets a new server: about 80% when going from 4 to 5 servers, and about 99% when going from 100 to 101. The cache is effectively empty, and all traffic hits the database.
Q2How does consistent hashing work?
Servers and keys are hashed onto the same ring. Each key belongs to the first server clockwise. A new server only takes over the arc just before it. A removed server hands its arc to the next server. So only about 1/N of the keys move.
Q3What are virtual nodes, and why use them?
Each physical server is placed at many points on the ring. This evens out the arc sizes, so the load is even. It also spreads the keys of a failed server across many servers instead of one neighbour. And bigger machines can take more load by having more vnodes.
Q4How is replication done on a ring?
A key is stored on its owner plus the next N−1 distinct physical servers clockwise. Ideally these servers are in different racks or availability zones. Dynamo, Cassandra and Riak work this way, and they combine it with read/write quorums.
Q5Does consistent hashing solve hot keys?
No. One very popular key still maps to one server. Handle hot keys by replicating that key, by caching it locally, by splitting the key, or by using consistent hashing with bounded loads.
Q6What's rendezvous hashing?
For each key, score every server with hash(key, server) and pick the highest score. It moves few keys and spreads them evenly, with no ring or vnodes. Each lookup costs O(N), which is fine for tens or hundreds of servers.
Key Takeaways
- Modulo hashing (
hash % N) reshuffles most keys when N changes. This is very bad for caches and for data placement. - Consistent hashing places servers and keys on a ring. Each key goes to the next server clockwise, so adding or removing a server moves only about 1/N of keys.
- Virtual nodes give even distribution, spread load after failures, and support servers of different sizes.
- The ring also decides replica placement in Dynamo-style databases.
- Know the alternatives: rendezvous hashing, jump hash, Maglev and bounded loads. Remember that consistent hashing does not fix hot keys.
Further Reading
- System Design Interview by Alex Xu (chapter 5, "Design Consistent Hashing")
- The paper "Consistent Hashing and Random Trees" by Karger et al. (STOC 1997)
- The paper "A Fast, Minimal Memory, Consistent Hash Algorithm" (jump consistent hash) by Lamping and Veach
- Tom White's article "Consistent Hashing"
- Damian Gryski's article "Consistent Hashing: Algorithmic Tradeoffs"
- The Google Research article "Consistent Hashing with Bounded Loads"
Published by Jainik · Last updated · How these lessons are made
Keep learning
Part of the study guide
Related series