Implement a Consistent Hash Ring in Python
Build a minimal consistent hash ring with virtual nodes to map keys to servers stably as nodes are added or removed.
Python code
37 linesimport hashlib
import bisect
class ConsistentHashRing:
def __init__(self, nodes=None, replicas=3):
self.replicas = replicas
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):
vnode_key = f"{node}-{i}"
h = self._hash(vnode_key)
self.ring[h] = node
bisect.insort(self.sorted_keys, h)
def get_node(self, key):
if not self.ring:
return None
h = self._hash(key)
idx = bisect.bisect_right(self.sorted_keys, h)
if idx == len(self.sorted_keys):
idx = 0
return self.ring[self.sorted_keys[idx]]
if __name__ == "__main__":
ring = ConsistentHashRing(["node-a", "node-b", "node-c"])
for k in ["user-1", "user-2", "user-3", "user-4", "user-5"]:
print(f"{k} -> {ring.get_node(k)}")
Output
user-1 -> node-c
user-2 -> node-a
user-3 -> node-b
user-4 -> node-a
user-5 -> node-b
How it works
The ring maps each node to several virtual node positions using MD5 hashes, spreading keys more evenly. bisect.insort keeps the hash list sorted so lookup uses binary search. get_node finds the first hash >= key's hash, wrapping to the start if needed. Adding or removing a node only remaps keys near the changed positions, not the whole ring.
Common mistakes
- Using a weak hash like Python's built-in hash() which isn't stable across runs
- Forgetting virtual nodes each have a unique key, causing collisions
- Not wrapping around to the first node when the key hashes beyond the last ring position
Variations
- Use `hashlib.sha1` or `sha256` for stronger distribution
- Replace `bisect` with `sortedcontainers` for easier rank-based operations
Real-world use cases
- Distributing cache keys across a Redis cluster for even load.
- Routing incoming requests to backend servers in a web load balancer.
- Partitioning data across database shards with minimal remapping during scaling.
Sponsored
More from System design patterns
- Build a BFF (Backend for Frontend) Mock Aggregator in Python medium
- Builder pattern for mocking complex objects in Python easy
- Circuit Breaker Pattern in Python: Closed, Open, and Half-Open States medium
- Create a Data Helper Class in Python easy
- Domain Driven Design Aggregate Root Example in Python medium
- Facade Pattern in Python with Mock Simplification medium
Keep learning
Related tutorials and quizzes for this topic.