Skip to content
About
Main content

Consistent and rendezvous hashing

How do you keep most keys in place when the set of nodes changes, and when is rendezvous better than the ring?

Consistent hashing

The problem it solves

With hash(key) % N, changing N by one reassigns about (N−1)/N of all keys.

For a cache, that is a near-total miss storm, at the exact moment when you are already weakened (a node just died). For a stateful it is a complete redistribution of state. The naive scheme is unstable under the one event you must handle gracefully.

The mechanism

Map both the keys and the nodes onto a ring: a hash space that wraps around at its maximum value. A key belongs to the first node found going clockwise from the key's position.

        node A
      /        \
   key1          key2 ---> belongs to node B (next clockwise)
     |            |
   node D       node B
      \        /
        node C

Add or remove a node and only the keys between that node and its predecessor move. The expected reassignment is K/N, not K·(N−1)/N, where K is the number of keys. Going from 10 nodes to 11 moves about 1/11 ≈ 9% of the keys instead of ~91%. That is the entire point, and it is a large constant-factor difference.

the number of keys
nodes after the change
modulo hashingconsistent hashing (ring)
Data table
Share of keys that move when one node is added
Nodes NseriesKeys moved
2modulo hashing50%
2.12modulo hashing52.8%
2.24modulo hashing55.4%
2.38modulo hashing57.9%
2.52modulo hashing60.3%
2.67modulo hashing62.5%
2.83modulo hashing64.6%
2.99modulo hashing66.6%
3.17modulo hashing68.5%
3.36modulo hashing70.2%
3.56modulo hashing71.9%
3.77modulo hashing73.5%
3.99modulo hashing74.9%
4.23modulo hashing76.3%
4.48modulo hashing77.7%
4.74modulo hashing78.9%
5.02modulo hashing80.1%
5.32modulo hashing81.2%
5.64modulo hashing82.3%
5.97modulo hashing83.3%
6.32modulo hashing84.2%
6.7modulo hashing85.1%
7.1modulo hashing85.9%
7.52modulo hashing86.7%
7.96modulo hashing87.4%
8.43modulo hashing88.1%
8.93modulo hashing88.8%
9.46modulo hashing89.4%
10modulo hashing90%
10.6modulo hashing90.6%
11.2modulo hashing91.1%
11.9modulo hashing91.6%
12.6modulo hashing92.1%
13.4modulo hashing92.5%
14.2modulo hashing92.9%
15modulo hashing93.3%
15.9modulo hashing93.7%
16.8modulo hashing94.1%
17.8modulo hashing94.4%
18.9modulo hashing94.7%
20modulo hashing95%
21.2modulo hashing95.3%
22.4modulo hashing95.5%
23.8modulo hashing95.8%
25.2modulo hashing96%
26.7modulo hashing96.3%
28.3modulo hashing96.5%
29.9modulo hashing96.7%
31.7modulo hashing96.8%
33.6modulo hashing97%
35.6modulo hashing97.2%
37.7modulo hashing97.3%
39.9modulo hashing97.5%
42.3modulo hashing97.6%
44.8modulo hashing97.8%
47.4modulo hashing97.9%
50.2modulo hashing98%
53.2modulo hashing98.1%
56.4modulo hashing98.2%
59.7modulo hashing98.3%
63.2modulo hashing98.4%
67modulo hashing98.5%
71modulo hashing98.6%
75.2modulo hashing98.7%
79.6modulo hashing98.7%
84.3modulo hashing98.8%
89.3modulo hashing98.9%
94.6modulo hashing98.9%
100modulo hashing99%
106modulo hashing99.1%
112modulo hashing99.1%
119modulo hashing99.2%
126modulo hashing99.2%
134modulo hashing99.3%
142modulo hashing99.3%
150modulo hashing99.3%
159modulo hashing99.4%
168modulo hashing99.4%
178modulo hashing99.4%
189modulo hashing99.5%
200modulo hashing99.5%
2consistent hashing (ring)50%
2.12consistent hashing (ring)47.2%
2.24consistent hashing (ring)44.6%
2.38consistent hashing (ring)42.1%
2.52consistent hashing (ring)39.7%
2.67consistent hashing (ring)37.5%
2.83consistent hashing (ring)35.4%
2.99consistent hashing (ring)33.4%
3.17consistent hashing (ring)31.5%
3.36consistent hashing (ring)29.8%
3.56consistent hashing (ring)28.1%
3.77consistent hashing (ring)26.5%
3.99consistent hashing (ring)25.1%
4.23consistent hashing (ring)23.7%
4.48consistent hashing (ring)22.3%
4.74consistent hashing (ring)21.1%
5.02consistent hashing (ring)19.9%
5.32consistent hashing (ring)18.8%
5.64consistent hashing (ring)17.7%
5.97consistent hashing (ring)16.7%
6.32consistent hashing (ring)15.8%
6.7consistent hashing (ring)14.9%
7.1consistent hashing (ring)14.1%
7.52consistent hashing (ring)13.3%
7.96consistent hashing (ring)12.6%
8.43consistent hashing (ring)11.9%
8.93consistent hashing (ring)11.2%
9.46consistent hashing (ring)10.6%
10consistent hashing (ring)9.98%
10.6consistent hashing (ring)9.42%
11.2consistent hashing (ring)8.89%
11.9consistent hashing (ring)8.39%
12.6consistent hashing (ring)7.92%
13.4consistent hashing (ring)7.48%
14.2consistent hashing (ring)7.06%
15consistent hashing (ring)6.67%
15.9consistent hashing (ring)6.29%
16.8consistent hashing (ring)5.94%
17.8consistent hashing (ring)5.61%
18.9consistent hashing (ring)5.3%
20consistent hashing (ring)5%
21.2consistent hashing (ring)4.72%
22.4consistent hashing (ring)4.46%
23.8consistent hashing (ring)4.21%
25.2consistent hashing (ring)3.97%
26.7consistent hashing (ring)3.75%
28.3consistent hashing (ring)3.54%
29.9consistent hashing (ring)3.34%
31.7consistent hashing (ring)3.15%
33.6consistent hashing (ring)2.98%
35.6consistent hashing (ring)2.81%
37.7consistent hashing (ring)2.65%
39.9consistent hashing (ring)2.51%
42.3consistent hashing (ring)2.37%
44.8consistent hashing (ring)2.23%
47.4consistent hashing (ring)2.11%
50.2consistent hashing (ring)1.99%
53.2consistent hashing (ring)1.88%
56.4consistent hashing (ring)1.77%
59.7consistent hashing (ring)1.67%
63.2consistent hashing (ring)1.58%
67consistent hashing (ring)1.49%
71consistent hashing (ring)1.41%
75.2consistent hashing (ring)1.33%
79.6consistent hashing (ring)1.26%
84.3consistent hashing (ring)1.19%
89.3consistent hashing (ring)1.12%
94.6consistent hashing (ring)1.06%
100consistent hashing (ring)0.998%
106consistent hashing (ring)0.942%
112consistent hashing (ring)0.889%
119consistent hashing (ring)0.839%
126consistent hashing (ring)0.792%
134consistent hashing (ring)0.748%
142consistent hashing (ring)0.706%
150consistent hashing (ring)0.667%
159consistent hashing (ring)0.629%
168consistent hashing (ring)0.594%
178consistent hashing (ring)0.561%
189consistent hashing (ring)0.53%
200consistent hashing (ring)0.5%
Adding an eleventh node100 keys · MD5 · 10 → 11 nodes

Virtual nodes: required, not optional

With one ring position per node, the arc lengths are random and vary a lot: some nodes own 3 times more than others. Worse, removing a node dumps its whole load on exactly one successor, and that is often enough to knock that node over too. That is cascading failure by design.

The fix: give each physical node V ring positions (V is typically 100–256). Then:

  • Load evens out. (With V = 100 the typical spread is about 10%; with V = 256, about 6%.)
  • A departure spreads over many successors instead of one. This is the more important benefit, and the one people forget.
V (ring positions per node)
— independent observations: registers, retained hashes, ring positions
  • Different capacities are easy to express. A node with 2× capacity gets 2V positions. Weighting is free.

The cost: V times more ring memory, and a slightly slower lookup (a binary search over N·V positions, still trivial).

import bisect, hashlib

def h(s: str) -> int:
    return int(hashlib.md5(s.encode()).hexdigest(), 16)

class Ring:
    def __init__(self, nodes, vnodes=128):
        self.ring = sorted((h(f"{n}#{i}"), n) for n in nodes for i in range(vnodes))
        self.points = [p for p, _ in self.ring]

    def node_for(self, key: str) -> str:
        i = bisect.bisect(self.points, h(key)) % len(self.ring)   # first point clockwise
        return self.ring[i][1]

Bounded loads

Plain balances keys, not load. Real keys have very different request rates. Consistent hashing with bounded loads caps each node at (1 + ε) × average load. If the target node is at its cap, the lookup walks clockwise to the next node that has room. This keeps most of the stability and also enforces a real load bound. It is used by HAProxy and Vimeo, and it comes from work at Google. Know it, because the failure of plain consistent hashing under skewed request rates surprises people.


Rendezvous (HRW) hashing

For each key, compute hash(key, node_i) for every node, then pick the node with the highest value. That is the whole algorithm. ( means highest random weight.)

def rendezvous(key: str, nodes: list[str]) -> str:
    return max(nodes, key=lambda n: h(f"{key}|{n}"))

Why it is often better than the ring

  • No ring, no virtual nodes, no state. Any client can compute the mapping from the node list alone.
  • Optimal disruption. Remove a node and only its keys move, and they spread across all remaining nodes according to their next-highest score. Add a node and only the keys for which it now scores highest move. This is provably the minimum, and it is better than plain consistent hashing without needing to repair it.
  • Better distribution than a ring with a modest V, with no tuning parameter.
  • An ordered fallback for free. The top-k scores give a natural list and a natural failover order, and every client computes the same list without coordination.
  • Weights through score = -w / ln(h), where h is the hash scaled to a number between 0 and 1 (weighted rendezvous). This is clean and exact.

The one real cost

A lookup is O(N) (it touches every node), against O(log N) for the ring. At N = 10 nodes this is irrelevant: ten hash computations. At N = 10,000 it matters, and you need hierarchical rendezvous (a tree of clusters with a branching factor, giving O(log N)).

When to use which

Consistent hashing (ring)Rendezvous
Node countlarge (hundreds to thousands)small to moderate (under a few hundred)
Lookup costO(log (N·V))O(N)
Needs virtual nodesyesno
Replica listwalk the ring; take care to skip positions on the same hosttop-k, natural
Distribution qualitygood with a high Vbetter, no tuning
Implementationabout 100 lines, easy to get subtly wrongabout 10 lines, to get wrong

An honest assessment: for most application-level cases (client-side load balancing, choosing a cache node, shard assignment with tens of nodes) rendezvous is the better default and is underused. Consistent hashing dominates in practice largely for historical reasons (the influence of Amazon's Dynamo paper) and because it is genuinely better at very large N.