Consistent hashing
In one line
Consistent hashing places keys and servers on the same circle so that adding or removing one server moves only about 1/N of the keys, instead of almost all of them.
What it is
When data or traffic is spread over N servers, you need a rule that maps each key (a user id, a cache key, a chat room) to one server. The naive rule is server = hash(key) mod N. It is simple and even, but when N changes, almost every key maps to a different server. For a cache that means a sudden flood of misses; for a database it means moving most of the data.
Consistent hashing fixes this. Imagine a circle of hash values from 0 to 2^64. Each server is hashed to one or more points on the circle. A key is hashed onto the same circle and belongs to the first server found by walking clockwise. When a server joins, it takes over only the keys between its point and the previous point. When it leaves, only its keys move to the next server. On average only K/N keys move (K keys, N servers).
With one point per server the load is uneven, because random points leave some servers with big arcs. The standard fix is virtual nodes: each physical server is placed at 100 to 256 points. Load evens out, and a server with twice the capacity can simply get twice as many virtual nodes.
Consistent hashing is one family of answers. Related schemes you should know by name: rendezvous (highest random weight) hashing, Google's jump consistent hash, Maglev hashing for load balancers, and fixed hash slots as in Redis Cluster (16,384 slots assigned to nodes), which is not a ring but solves the same problem.
Why interviewers ask
Any design that shards a cache, a key-value store, WebSocket sessions or a work queue by key needs a mapping that survives scaling and failures. Interviewers ask about consistent hashing in distributed cache, key-value store, chat and URL shortener rounds to see whether you know why mod N fails, how virtual nodes fix skew, and how replication works on the ring. It is also a common trap: candidates say 'consistent hashing' as a magic word without explaining what problem it solves.
How it works
- 1Pick a hash function with good spread (MurmurHash3, xxHash, or MD5 truncated to 64 bits). It does not need to be cryptographic, but every client must use the same one.
- 2Place each server on the ring at many points by hashing names like 'cache-a#0', 'cache-a#1' ... 'cache-a#149'. Keep the points in a sorted array.
- 3To find a key's owner, hash the key and binary-search the sorted array for the first point greater than or equal to it, wrapping to the start if you pass the end. Lookup is O(log V) where V is the total number of virtual nodes.
- 4To replicate, walk clockwise and take the next R distinct physical servers (skipping more virtual nodes of a server already chosen). This is how Dynamo-style stores pick the replica set (the 'preference list') for a key.
- 5When a server is added, it takes ownership of the arcs just before each of its virtual nodes, pulling those keys from their old owners. About 1/(N+1) of the keys move.
- 6When a server fails or is removed, its arcs fall to the next servers clockwise. Because its virtual nodes are scattered, the extra load is spread across many servers instead of landing on one neighbour.
- 7Keep ring membership consistent across clients: a config service (ZooKeeper, etcd), a gossip protocol (Cassandra) or a smart client library that refreshes the server list. Two clients with different rings will read and write different servers.
Worked example · Adding a fifth cache node to a 4-node Redis cache fleet
- A product catalogue cache holds 100M keys on 4 nodes, about 25M keys each, serving 200,000 reads per second with a 95% hit rate.
- With hash(key) mod 4, adding a fifth node changes the rule to mod 5. A key stays on the same node only if hash mod 4 equals hash mod 5, which is true for 4 out of every 20 hash values, so about 80% of keys (80M) suddenly map to a different node.
- Those 80M keys are misses until refilled. The hit rate falls from 95% toward 20%, so database reads jump from about 10,000 per second to about 160,000 per second. That is a self-inflicted outage.
- With a consistent hash ring and 150 virtual nodes per server, the new node takes about 1/5 of the circle. Only about 20M keys (20%) move, all of them to the new node. The other 80M keys stay where they are.
- Misses rise briefly for those 20M keys: database reads go up to roughly 50,000 per second for a few minutes while the new node warms. You can soften even that by adding the node during low traffic or warming it from the old owners first.
Picture it
Think of a round dining table with waiters standing at fixed spots around it. Each guest is served by the next waiter clockwise. If one more waiter joins, they take over only the guests between themselves and the waiter before them; nobody else at the table changes waiter. With 'mod N', every guest would be reassigned each time a waiter joined or left.
Reference code
import bisect
import hashlib
def h(value: str) -> int:
# Any well-mixed hash works; md5 is used here only for spread, not security
return int(hashlib.md5(value.encode()).hexdigest()[:16], 16)
class HashRing:
def __init__(self, nodes, vnodes=150):
self.vnodes = vnodes
self.ring = [] # sorted list of (position, node)
for node in nodes:
self.add(node)
def add(self, node):
for i in range(self.vnodes):
bisect.insort(self.ring, (h(f"{node}#{i}"), node))
def remove(self, node):
self.ring = [(p, n) for p, n in self.ring if n != node]
def node_for(self, key):
pos = h(key)
i = bisect.bisect_right(self.ring, (pos, chr(0x10FFFF)))
return self.ring[i % len(self.ring)][1] # wrap around the circle
ring = HashRing(["cache-a", "cache-b", "cache-c", "cache-d"])
print(ring.node_for("user:42")) # same answer on every app server
ring.add("cache-e") # only ~1/5 of keys now map to a new nodeKey patterns
Virtual nodes
When: Always, unless you have very few servers and do not care about skew
How: Place each physical server at 100 to 256 points. More points give smoother load at the cost of a bigger ring in memory. Weight servers by giving bigger machines more points.
Replication by walking the ring
When: Stores that keep R copies of each key
How: Take the owner plus the next R-1 distinct physical servers clockwise; add rack or zone awareness so copies do not share a failure domain.
Consistent hashing with bounded loads
When: Keys have very uneven popularity, as in load balancing
How: Give each server a capacity of about (1 + epsilon) times the average load; if the chosen server is full, move to the next one clockwise. Google published this in 2017 and Vimeo applied it to HAProxy.
Fixed slots instead of a ring
When: You want simple, operator-controlled rebalancing
How: Hash keys into a fixed number of slots or partitions (Redis Cluster 16,384; Kafka partitions) and keep a slot-to-node map. Moving a slot is an explicit, observable operation.
Real-world use
Amazon Dynamo and DynamoDB
The 2007 Dynamo paper used a consistent hash ring with virtual nodes to partition data and chose replicas by walking the ring. It became the template for Dynamo-style stores.
Apache Cassandra
Each node owns token ranges on a ring; virtual nodes (num_tokens) spread ranges so a new node streams data from many peers at once. Replicas are the next nodes on the ring, made rack and data-centre aware by the replication strategy.
Memcached client libraries (ketama)
Memcached servers do not talk to each other; the client hashes keys onto a ketama ring so the same key always goes to the same server, and adding a server invalidates only a fraction of the cache.
Redis Cluster
Uses 16,384 fixed hash slots (CRC16(key) mod 16384) assigned to nodes instead of a ring. Resharding moves whole slots. It is a good example to contrast with a ring in interviews.
Load balancers (Google Maglev, Envoy, HAProxy)
Maglev hashing and ring-hash load balancing keep a connection or user pinned to the same backend while backends come and go. Envoy offers ring_hash and maglev policies; HAProxy supports consistent hashing with bounded loads.
Discord
Discord has described routing each guild (server) to a session process using a consistent hash ring, so a guild's state lives on one node and moves minimally when nodes change.
In a project
You run the WebSocket gateway for a food delivery app. Each order has a live tracking channel, and all subscribers of one order must be on the same pub-sub node. During festive evenings you scale from 20 to 40 nodes, and with mod N routing every scale event dropped and re-created most channels.
- Map order_id to a pub-sub node with a consistent hash ring, 128 virtual nodes per server, kept in etcd and watched by every gateway.
- When scaling from 20 to 21 nodes, only about 1/21 of channels (about 5%) move, so most live tracking screens are not disturbed.
- Add nodes one at a time with a short delay, rather than 20 at once, so the moved channels re-subscribe gradually.
- Use bounded-load consistent hashing (cap each node at about 1.25x the average load) so one very busy channel area does not overload a node.
- On node failure, its virtual nodes are spread around the ring, so its channels spread across many survivors instead of doubling one neighbour's load.
Impact: Scaling events now reconnect about 5% of tracking sessions instead of 95%, peak reconnect storms disappear, and the team can autoscale the gateway during dinner peaks without user-visible glitches.
Trade-offs
Use it when
- Sharding a cache, key-value store or session store whose node count changes often
- Pinning users, rooms or orders to a server so state stays local (chat rooms, game sessions, tracking channels)
- Clients route directly to nodes without a central lookup service
- Load balancing where stickiness to a backend helps local caches
Avoid it when
- The number of nodes is fixed and small; plain mod N or a static map is simpler
- You need range scans over keys; hashing destroys key order, so use range partitioning instead
- A few keys are extremely hot; consistent hashing spreads keys, not load per key, so you still need hot-key handling
- You want explicit, auditable data movement; fixed slots or partitions with a placement map are easier to operate
Common mistakes
- Saying 'consistent hashing' without explaining that mod N remaps almost all keys when N changes.
- Forgetting virtual nodes, then being surprised that one server owns 2x the keys of another.
- Assuming consistent hashing solves hot keys. A celebrity key still lands on one server.
- Different clients holding different versions of the ring, so reads miss data that writes put elsewhere.
- Claiming Redis Cluster uses a hash ring; it uses 16,384 fixed hash slots.
- Choosing replicas as the next R virtual nodes without skipping duplicates, so two copies land on the same machine.
Interview questions
Quick revision
- mod N: adding 1 node to N moves about N/(N+1) of keys. Ring: about 1/(N+1).
- Key owner = first server point clockwise from hash(key).
- Virtual nodes (100-256 per server) fix skew and allow weighting.
- Replicas = next R distinct physical nodes clockwise, zone-aware.
- Lookup O(log V) by binary search on sorted points.
- Spreads keys, not per-key load: hot keys need separate handling.
- Membership must be consistent: etcd/ZooKeeper, gossip or a proxy.
- Alternatives: rendezvous hashing, jump hash, Maglev, fixed slots (Redis Cluster 16,384).
Check yourself
1. You have 4 cache servers using hash(key) mod 4 and add a fifth. Roughly what fraction of keys now map to a different server?
2. What problem do virtual nodes solve?
3. A celebrity's profile key gets 50,000 reads per second and overloads one cache node in a consistent hash ring. What helps most?
4. How does Redis Cluster map keys to nodes?
See it in a full design
These walkthroughs use this lesson.
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.