Consistent Hashing
Purpose
Own one function: given a key and a set of nodes, which node holds it — and how much of that
mapping survives when a node joins or leaves. Nothing else in the partitioning family
computes placement; this skill is where any hashing arithmetic belongs.
The failure this prevents is hash(key) % N. With a sufficiently uniform hash it can
distribute keys evenly, but it stays operationally stable only
until N changes, at which point a large fraction may map somewhere new — for a cache a
fleet-wide miss storm in one step, for a store a migration of nearly the whole dataset,
discovered when someone adds a node to relieve pressure and the rebalance becomes the outage.
The second failure is subtler: a ring with one point per node is not well balanced, so a
naive implementation gets minimal disruption while handing one node several times another's
share.
Workflow
- State the disruption and migration budget. How many keys, bytes and requests may
change owner, at what transfer rate, and under what availability target?
% N can remap
a large fraction; with equal nodes, a ring or rendezvous moves about K/(N+1) on a join and
the removed node's approximately K/N share on a removal.
- Count the nodes. With a small membership, rendezvous hashing is fewer
moving parts than a ring and needs no virtual-node tuning. A ring earns its complexity at
larger N or where lookup must be sub-linear.
- Specify the placement contract completely: algorithm and variant, seed, byte encoding,
field framing, signed/unsigned ordering, virtual-point format and membership epoch. Prove
cross-runtime agreement with golden vectors. MurmurHash3 or xxHash can be suitable when
the exact implementation is pinned. See
references/mapping-functions.md for what disqualifies the obvious candidates.
- Pick V by measurement, not by folklore. Simulate representative keys, bytes, request
rates and per-key cost over relevant node counts and seeds. Raise virtual points until the
worst load/mean is inside tolerance, then measure lookup and rebuild cost.
- Implement the wrap-around explicitly.
ceilingEntry(h) returning null means the key
hashed past the last point on the ring and belongs to the first entry. This single branch
is the most commonly omitted line in the pattern.
- Separate invariants from statistical expectations. With existing points unchanged,
a join may move keys only to the new node; a removal may move only the removed node's
keys. Measure movement against K/(N+1) or K/N using a justified tolerance, not a hard
upper bound. See
references/ring-in-java.md for implementation and tests.
- Model heterogeneous capacity explicitly. Proportional virtual-point counts are one
coarse mechanism, but CPU, memory, I/O and workload costs may not scale together. Prefer
fixed logical partitions or an assignment service when placement needs constraints.
- Treat membership as a data migration. Publish a new epoch, copy and verify the newly
owned ranges, coordinate reads/writes during handoff, activate the epoch, and retire old
owners only after stale clients and in-flight work are bounded. Minimal remapping is not a
migration protocol.
Decision block
For Java changes, inspect compiler release/toolchains, runtime images and resolved hash
library versions first. The ring example requires Java 16+ syntax/APIs and Guava; this is
an example prerequisite, not authorization to upgrade the target or add a dependency.
If topology or workload evidence is missing, keep the algorithm/V recommendation conditional
and identify the simulation or measurement needed. Deliver the placement contract, chosen
trade-off, movement/balance evidence and handoff risks; keep simple reviews concise.
Use a ring with virtual nodes when:
- membership changes are routine (autoscaling, rolling replacement) and the disruption
budget forbids remapping the whole keyspace
- N is large enough that O(log N) lookup matters, or nodes have different capacities and
weighting by virtual-node count is the natural expression of that
Use rendezvous (highest random weight) hashing when:
- N is small enough for O(N) hashes per lookup within the measured budget; it avoids
virtual-node tuning and gives probabilistically even shares, even with frequent churn
- you need the ordered list of candidates for a key (primary, then replicas) — rendezvous
produces it directly, with probabilistic balance under a suitable hash
Use bounded-load consistent hashing when:
- the chosen algorithm's capacity unit matches the workload, allocation state is agreed,
and displacement is acceptable; a key-count cap alone does not bound bytes or QPS
Use hash(key) % N when:
- N is fixed for the lifetime of the data, and changing it is understood to be a full
migration — a fixed set of logical partitions, for example, later mapped to physical nodes
Prefer a directory (sharding-and-partitioning) instead when:
- placement must be decided per key rather than computed — pinning a known-large tenant to
its own node is a placement policy, and no hash function expresses it
Rules
hash(key) % N can remap a large fraction when N changes. For uniform residues on an
N-to-N+1 change, about N/(N+1) move; not literally every key. Fix logical partition count
independently of physical membership, or explicitly budget a rehash migration.
- With equal nodes, a join moves about K/(N+1) keys to the new node; removing one moves
its about K/N share. These are expectations over the hash and key population. This is
not a guarantee for a particular key set, and it says nothing about how much traffic
moves.
- One point per node has high variance. The shares are the gaps between N random points
on a circle; virtual nodes
exist for that, not for the disruption property, which the plain ring already has.
- Without virtual nodes, removing a node transfers its entire share to one successor. That
successor's increment equals the failed node's share; it is not necessarily a doubling.
With V virtual points the departing ranges usually spread across several successors.
- V costs memory and lookup time: the ring holds
V × N entries, so lookup is O(log(V×N))
and construction is O(V×N log(V×N)) with ordinary ordered-map insertion. Rebuild, snapshot
publication and cache effects must be measured; V in the thousands per node is a data
structure, not merely a tuning knob.
- The hash must produce the same value in every process, on every JDK, forever. Two
clients that disagree about placement are two clients writing the same key to different
owners. This rules out
Object.hashCode() (identity-based), record and enum hashCode()
(unspecified), and any library hash documented as version-unstable — Guava's
Hashing.goodFastHash says so explicitly, while Hashing.murmur3_128() names a fixed
algorithm.
String.hashCode() is specified and deterministic, but it is only 32 bits and was not
designed as a placement hash. Its distribution depends on the actual key set; do not infer
pathological clustering from prefixes alone. Evaluate representative keys and prefer a
pinned 64-bit-or-wider hash with good avalanche. A cryptographic or keyed hash may be
justified for adversarial keys, at additional CPU cost.
- Consistent hashing distributes keys, never traffic. A perfectly even ring with one
celebrity key still has one saturated node. That is not a hashing bug and no value of V
fixes it — the diagnosis and the repairs are
hot-partitions-and-rebalancing.
- Every participant must agree on the ring: the membership epoch, node set and weights, the
virtual-node count, the full hash contract, and the exact string hashed to place a virtual
node (
"node-3#7" is not
"node-3-7"). Version the membership and treat a change as a coordinated deployment, or
route through one component that owns it. During a transition, old and new epochs need an
explicit handoff and fencing policy; eventual membership dissemination alone permits
split ownership and lost writes.
- Replication follows the ring by walking clockwise to the next R distinct physical nodes
— skipping further virtual nodes of a node already chosen. Forgetting the distinctness
check places every replica of a key on one machine, which is the failure the replication
was bought to prevent.
- Do not use this to spread requests over interchangeable replicas. Consistent hashing pins a
key to an owner deliberately; a least-request policy deliberately does not, and
load-balancing-and-routing owns that decision.
References
Consistent hashing and random trees
— the original consistent-hashing model and disruption result.
The ring in Java — a collision-safe TreeMap<RingPoint, String> ring with
virtual nodes, add and remove, the wrap-around branch, replica selection across distinct
physical nodes, the hash-stability requirement in code, and a test that measures the
fraction of keys that move when a node is added. Read when implementing or reviewing
placement code.
Choosing the mapping function — modulo, ring with
virtual nodes, rendezvous and bounded-load compared on disruption, lookup cost,
distribution quality and implementation complexity, with a decision table and the hash
function shortlist. Read when choosing between them, or when justifying a ring over the
simpler option.
1---2name: consistent-hashing3description: Stable key-to-node placement across membership changes: modulo remapping, consistent-hash rings, virtual points, rendezvous hashing, collision-safe Java implementations, hash contracts, replica selection, weighting, testing and membership handoff. Use when changing node count causes a miss storm or migration, ownership is uneven, or placement relies on Object.hashCode. Does not choose the shard key (sharding-and-partitioning), repair hot keys (hot-partitions-and-rebalancing), define cache topology (cache-sharding-and-replication), or balance interchangeable replicas (load-balancing-and-routing).4---56# Consistent Hashing78## Purpose910Own one function: given a key and a set of nodes, which node holds it — and how much of that11mapping survives when a node joins or leaves. Nothing else in the partitioning family12computes placement; this skill is where any hashing arithmetic belongs.1314The failure this prevents is `hash(key) % N`. With a sufficiently uniform hash it can15distribute keys evenly, but it stays operationally stable only16until N changes, at which point a large fraction may map somewhere new — for a cache a17fleet-wide miss storm in one step, for a store a migration of nearly the whole dataset,18discovered when someone adds a node to relieve pressure and the rebalance becomes the outage.19The second failure is subtler: a ring with one point per node is _not_ well balanced, so a20naive implementation gets minimal disruption while handing one node several times another's21share.2223## Workflow24251. **State the disruption and migration budget.** How many keys, bytes and requests may26 change owner, at what transfer rate, and under what availability target? `% N` can remap27 a large fraction; with equal nodes, a ring or rendezvous moves about K/(N+1) on a join and28 the removed node's approximately K/N share on a removal.292. **Count the nodes.** With a small membership, rendezvous hashing is fewer30 moving parts than a ring and needs no virtual-node tuning. A ring earns its complexity at31 larger N or where lookup must be sub-linear.323. **Specify the placement contract completely:** algorithm and variant, seed, byte encoding,33 field framing, signed/unsigned ordering, virtual-point format and membership epoch. Prove34 cross-runtime agreement with golden vectors. MurmurHash3 or xxHash can be suitable when35 the exact implementation is pinned. See36 `references/mapping-functions.md` for what disqualifies the obvious candidates.374. **Pick V by measurement, not by folklore.** Simulate representative keys, bytes, request38 rates and per-key cost over relevant node counts and seeds. Raise virtual points until the39 worst load/mean is inside tolerance, then measure lookup and rebuild cost.405. **Implement the wrap-around explicitly.** `ceilingEntry(h)` returning `null` means the key41 hashed past the last point on the ring and belongs to the first entry. This single branch42 is the most commonly omitted line in the pattern.436. **Separate invariants from statistical expectations.** With existing points unchanged,44 a join may move keys only to the new node; a removal may move only the removed node's45 keys. Measure movement against K/(N+1) or K/N using a justified tolerance, not a hard46 upper bound. See `references/ring-in-java.md` for implementation and tests.477. **Model heterogeneous capacity explicitly.** Proportional virtual-point counts are one48 coarse mechanism, but CPU, memory, I/O and workload costs may not scale together. Prefer49 fixed logical partitions or an assignment service when placement needs constraints.508. **Treat membership as a data migration.** Publish a new epoch, copy and verify the newly51 owned ranges, coordinate reads/writes during handoff, activate the epoch, and retire old52 owners only after stale clients and in-flight work are bounded. Minimal remapping is not a53 migration protocol.5455## Decision block5657For Java changes, inspect compiler release/toolchains, runtime images and resolved hash58library versions first. The ring example requires Java 16+ syntax/APIs and Guava; this is59an example prerequisite, not authorization to upgrade the target or add a dependency.60If topology or workload evidence is missing, keep the algorithm/V recommendation conditional61and identify the simulation or measurement needed. Deliver the placement contract, chosen62trade-off, movement/balance evidence and handoff risks; keep simple reviews concise.6364```text65Use a ring with virtual nodes when:66- membership changes are routine (autoscaling, rolling replacement) and the disruption67 budget forbids remapping the whole keyspace68- N is large enough that O(log N) lookup matters, or nodes have different capacities and69 weighting by virtual-node count is the natural expression of that70Use rendezvous (highest random weight) hashing when:71- N is small enough for O(N) hashes per lookup within the measured budget; it avoids72 virtual-node tuning and gives probabilistically even shares, even with frequent churn73- you need the ordered list of candidates for a key (primary, then replicas) — rendezvous74 produces it directly, with probabilistic balance under a suitable hash75Use bounded-load consistent hashing when:76- the chosen algorithm's capacity unit matches the workload, allocation state is agreed,77 and displacement is acceptable; a key-count cap alone does not bound bytes or QPS78Use hash(key) % N when:79- N is fixed for the lifetime of the data, and changing it is understood to be a full80 migration — a fixed set of logical partitions, for example, later mapped to physical nodes81Prefer a directory (sharding-and-partitioning) instead when:82- placement must be decided per key rather than computed — pinning a known-large tenant to83 its own node is a placement policy, and no hash function expresses it84```8586## Rules8788- `hash(key) % N` can remap a large fraction when N changes. For uniform residues on an89 N-to-N+1 change, about N/(N+1) move; not literally every key. Fix logical partition count90 independently of physical membership, or explicitly budget a rehash migration.91- With equal nodes, a join moves **about K/(N+1)** keys to the new node; removing one moves92 its **about K/N** share. These are expectations over the hash and key population. This is93 not a guarantee for a particular key set, and it says nothing about how much _traffic_94 moves.95- **One point per node has high variance.** The shares are the gaps between N random points96 on a circle; virtual nodes97 exist for that, not for the disruption property, which the plain ring already has.98- Without virtual nodes, removing a node transfers its entire share to one successor. That99 successor's increment equals the failed node's share; it is not necessarily a doubling.100 With V virtual points the departing ranges usually spread across several successors.101- V costs memory and lookup time: the ring holds `V × N` entries, so lookup is O(log(V×N))102 and construction is O(V×N log(V×N)) with ordinary ordered-map insertion. Rebuild, snapshot103 publication and cache effects must be measured; V in the thousands per node is a data104 structure, not merely a tuning knob.105- **The hash must produce the same value in every process, on every JDK, forever.** Two106 clients that disagree about placement are two clients writing the same key to different107 owners. This rules out `Object.hashCode()` (identity-based), record and enum `hashCode()`108 (unspecified), and any library hash documented as version-unstable — Guava's109 `Hashing.goodFastHash` says so explicitly, while `Hashing.murmur3_128()` names a fixed110 algorithm.111- `String.hashCode()` **is** specified and deterministic, but it is only 32 bits and was not112 designed as a placement hash. Its distribution depends on the actual key set; do not infer113 pathological clustering from prefixes alone. Evaluate representative keys and prefer a114 pinned 64-bit-or-wider hash with good avalanche. A cryptographic or keyed hash may be115 justified for adversarial keys, at additional CPU cost.116- Consistent hashing distributes **keys**, never **traffic**. A perfectly even ring with one117 celebrity key still has one saturated node. That is not a hashing bug and no value of V118 fixes it — the diagnosis and the repairs are `hot-partitions-and-rebalancing`.119- Every participant must agree on the ring: the membership epoch, node set and weights, the120 virtual-node count, the full hash contract, and the exact string hashed to place a virtual121 node (`"node-3#7"` is not122 `"node-3-7"`). Version the membership and treat a change as a coordinated deployment, or123 route through one component that owns it. During a transition, old and new epochs need an124 explicit handoff and fencing policy; eventual membership dissemination alone permits125 split ownership and lost writes.126- Replication follows the ring by walking clockwise to the next R **distinct physical** nodes127 — skipping further virtual nodes of a node already chosen. Forgetting the distinctness128 check places every replica of a key on one machine, which is the failure the replication129 was bought to prevent.130- Do not use this to spread requests over interchangeable replicas. Consistent hashing pins a131 key to an owner deliberately; a least-request policy deliberately does not, and132 `load-balancing-and-routing` owns that decision.133134## References135136- [Consistent hashing and random trees](https://www.cs.princeton.edu/courses/archive/fall09/cos518/papers/chash.pdf)137 — the original consistent-hashing model and disruption result.138139- [The ring in Java](references/ring-in-java.md) — a collision-safe `TreeMap<RingPoint, String>` ring with140 virtual nodes, add and remove, the wrap-around branch, replica selection across distinct141 physical nodes, the hash-stability requirement in code, and a test that measures the142 fraction of keys that move when a node is added. Read when implementing or reviewing143 placement code.144- [Choosing the mapping function](references/mapping-functions.md) — modulo, ring with145 virtual nodes, rendezvous and bounded-load compared on disruption, lookup cost,146 distribution quality and implementation complexity, with a decision table and the hash147 function shortlist. Read when choosing between them, or when justifying a ring over the148 simpler option.