Courses › System Design › Core Concepts

Consistent Hashing

The hash ring, virtual nodes, and why adding a node only moves 1/N of keys.

Consistent hashing answers a specific question: how do you spread keys across a changing set of nodes without moving most of the keys every time a node is added or removed? It underpins distributed caches, Cassandra, DynamoDB and many load balancers.

The problem with modulo hashing

The naive approach assigns a key to hash(key) % N, where N is the number of nodes. It distributes keys evenly — until N changes. Go from 4 to 5 nodes and roughly 80% of keys map to a different node. For a cache, that's a mass miss and a stampede on your database. For a datastore, it's a huge data migration.

The hash ring

Consistent hashing maps both nodes and keys onto the same circular hash space (say, 0 to 2³² − 1).

  • Hash each node's ID to a position on the ring.
  • Hash each key to a position on the ring.
  • Walk clockwise from the key's position; the first node you reach owns the key.

When you add a node, it takes over only the keys between it and its predecessor — about 1/N of the total. When a node is removed, its keys move to the next node clockwise. Everyone else is unaffected.

import bisect, hashlib

class HashRing:
    def __init__(self, nodes, vnodes=100):
        self.ring = []  # sorted list of (hash, node)
        for node in nodes:
            for i in range(vnodes):
                self.ring.append((self._hash(f"{node}#{i}"), node))
        self.ring.sort()

    def _hash(self, key):
        return int(hashlib.md5(key.encode()).hexdigest(), 16)

    def get_node(self, key):
        h = self._hash(key)
        idx = bisect.bisect(self.ring, (h,)) % len(self.ring)
        return self.ring[idx][1]

Virtual nodes

With only a few nodes, their random positions on the ring leave some nodes owning much bigger arcs than others. Virtual nodes fix this: each physical node is placed at many positions (often 100–256). This:

  • Evens out the load across nodes
  • Spreads a failed node's keys across *many* remaining nodes instead of dumping them all on one neighbor
  • Lets you give bigger machines more virtual nodes

Replication on the ring

For durability, store each key on the owning node and the next R−1 distinct nodes clockwise. That's exactly how Cassandra and Dynamo-style stores place replicas.

[!INTERVIEW] When to bring it up Mention consistent hashing when you're distributing a cache or a datastore across nodes and need to scale the cluster, or when you need "sticky" routing — for example, routing all edits to a document to the same server. You rarely need to implement it; you do need to explain why modulo hashing fails and what virtual nodes are for.