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.

Medium Python 3.9+ Aug 9, 2026 System design patterns 15 views 0 copies

Python code

37 lines
Python 3.9+
import 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

stdout
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

  1. Use `hashlib.sha1` or `sha256` for stronger distribution
  2. 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

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from System design patterns

Related tutorials and quizzes for this topic.