Consistent Hashing
Consistent Hashing: The Ring of Equilibrium
The Story: The Logic of the Campfire
Imagine a group of 10 scouts sitting in a large circle around a campfire. Each scout is responsible for catching any âfirefliesâ that fly into their section of the circle.
If a 11th scout joins the circle, everyone just scoots over a little bit. Only the two scouts next to the newcomer have to give up a small portion of their section. The other 8 scouts keep their fireflies exactly where they are.
Now, imagine if they were using a strict numbering system: âIf the fireflyâs ID is , Scout number catches it.â If an 11th scout joins, the formula changes to . Suddenly, almost every single firefly in the camp has to be handed over to a different scout. This is the Rehash Disaster.
In 1997, David Karger and his team at MIT proposed Consistent Hashing to solve this. It ensures that when the cluster size changes, only of the data needs to move.
Why do we need it?
Consistent hashing is the backbone of Scalability.
- Distributed Caching (Memcached/Redis): Adding a new cache server shouldnât invalidate your entire cache.
- Load Balancing: Distributing web requests across a fleet of servers.
- Distributed Databases (Cassandra/DynamoDB): Dividing billions of rows across hundreds of nodes.
How the Algorithm âThinksâ
The algorithm is a circular architect.
- The Ring: It imagines the entire range of possible hash values as a circle (the âHash Ringâ).
- Node Placement: It hashes the IDs of the Servers (Nodes) and places them at random points on this ring.
- Data Placement: It hashes the ID of the Data (e.g., a User ID) and places it on the ring.
- The Rule of the Clock: The data is assigned to the first node it encounters when moving clockwise around the ring.
- Virtual Nodes: To prevent one node from being âunluckyâ and getting too much data, it creates âVirtual Nodesââfake copies of each server spread all over the ring to ensure a perfect, even distribution.
Engineering Context: Minimal Disruption
- Node Add: When a node is added, it only âstealsâ data from its immediate counter-clockwise neighbor.
- Node Remove: When a node fails, its data simply âfalls forwardâ to the next neighbor on the ring.
- Stability: In a system with 1,000 nodes, adding 1 node only disrupts 0.1% of the data. Without consistent hashing, it would disrupt ~99.9%.
Implementation (Python)
import hashlib
class ConsistentHashRing:
def __init__(self, nodes=None, replicas=3):
self.replicas = replicas # Virtual nodes
self.ring = {}
self.sorted_keys = []
if nodes:
for node in nodes:
self.add_node(node)
def _hash(self, key):
return int(hashlib.md5(key.encode()).hexdigest(), 16)
def add_node(self, node):
for i in range(self.replicas):
h = self._hash(f"{node}:{i}")
self.ring[h] = node
self.sorted_keys.append(h)
self.sorted_keys.sort()
def get_node(self, data_key):
if not self.ring: return None
h = self._hash(data_key)
# Find the first node clockwise
for node_hash in self.sorted_keys:
if h <= node_hash:
return self.ring[node_hash]
# Wrap around to the first node
return self.ring[self.sorted_keys[0]]
# Usage
ring = ConsistentHashRing(["Server_A", "Server_B", "Server_C"])
print(f"User 'luke' goes to: {ring.get_node('luke')}")Summary
Consistent Hashing teaches us that true scale requires localizing change. By moving from a rigid global formula to a flexible circular arrangement, we can grow our systems without causing chaos. It reminds us that in a world of constant change, the best systems are those that can adapt without disturbing the peace of the whole.
