← back to journal

The Ring Needs a Thousand Points

2026-10-08 · Kit Wickham

When you spread keys over servers with hash(key) mod n, adding one server moves almost every key: going from 100 to 101 servers moved 99% of them in my runs, a hundred times the 1% that has to move. Consistent hashing is the family of schemes that move only that 1%. There are several, and they're usually compared on paper. I measured six of them on two things: how lopsided the busiest server gets, and how many keys move when a server joins or leaves, compared with the minimum.

The short version: the classic hash ring is badly lopsided unless you give each server hundreds to thousands of points, and the exact amount follows a clean formula. Rendezvous and jump hashing are as even as random assignment and move exactly the minimum. Maglev, Google's table-based scheme, moved 1.5 to 6.6 times the minimum. Capping each server's load works, but it costs extra moves unless you also use many points.

The schemes

Every run used a million random keys with 10, 100 or 1,000 servers and 20 seeds per setting, about 1,600 runs in all. "Keys moved" compares the assignment before and after one server joins (or a random one leaves) with the minimum: the newcomer's fair share, or the departed server's own keys.

Left: log-log chart of the busiest server's load divided by the average, against points on the ring per server from 1 to 1,024, for 10, 100 and 1,000 servers. Simulated dots sit on dashed theory curves. With one point the busiest server carries about 3 times the average for 10 servers, 5.3 for 100 and 7.2 for 1,000; all three curves fall to between 1.06 and 1.11 at 1,024 points. Right: bar chart of keys moved when a 101st server joins, as a multiple of the minimum. Rendezvous 0.99, jump 1.01, ring with 256 points 1.01, Maglev with 65,537 slots 1.55, Maglev going from 1,000 to 1,001 servers 6.60, load cap 1.1 with 100 points 1.23, load cap 1.25 with 1 point 6.89.
Left: ring imbalance, measured as exact arc shares so key noise doesn't blur it; 20 seeds per dot. Right: means over 20 seeds with a million keys; standard errors are under 0.07 except the one-point load cap (0.6).

The ring is lopsided, exactly as predicted

With one point per server, the busiest of 100 servers carried 5.3 times the average load; of 1,000 servers, 7.2 times. Arc lengths between random points on a circle are far from equal, and the busiest server is the one that drew the longest arc.

This has an exact answer. With v random points per server, the shares of the circle follow a Dirichlet distribution with every parameter equal to v, so the busiest server's load is the largest of n Gamma(v) draws divided by their mean. The dashed curves are that formula, computed numerically; 30 of the 33 simulated settings are within two standard errors of them and the rest within three. A good back-of-the-envelope version is 1 + √(2 ln n / v): for 16 or more points it was within 7% of the measured value. Flip it around and you get the points you need: about 2 ln n / ε2 to keep the busiest server within 1 + ε of average. For 100 servers and 10% that's about 900 points each, and 1,024 points measured 1.08×.

points per server10 servers100 servers1,000 servers
12.97×5.35×7.23×
161.49×1.67×2.01×
2561.11×1.16×1.23×
1,0241.06×1.08×1.11×

Busiest server's share of the ring divided by the average share, mean of 20 seeds.

Sixteen points per server is a common setting (it's Cassandra's default since 4.0), and with random placement it leaves the busiest of 100 servers at 1.67 times average. Cassandra pairs it with an allocator that places new points deliberately rather than at random, which is the right fix; random placement at 16 points isn't good enough.

More points stop helping at some point, because the keys themselves are random too. With 1,000 keys per server, even perfectly random assignment leaves the busiest server at about 1.10× average, and the ring reaches that at about 1,000 points per server. Past roughly as many points as each server has keys, you're paying memory for nothing.

Points also decide who inherits a dead server's keys. With one point, all of them land on a single neighbor, which doubles that neighbor's load. With 16 points, they spread over about 15 servers; with 256 points over 91 of the remaining 99.

Rendezvous and jump are as good as it gets

Both hit the floor set by key randomness: the busiest of 100 servers held 1.026× and 1.024× average against a random-assignment benchmark of 1.025×, and adding a server moved 0.995× and 1.006× the minimum. On balance and movement there is nothing left to improve. The costs are elsewhere. Rendezvous scores every server on every lookup: at 1,000 servers a run took 5.4 seconds against 0.3 for jump. Jump can't remove an arbitrary server, only the last bucket, so it suits numbered shards rather than a pool of machines that fail at random.

Maglev trades movement for speed

Maglev's table spread keys as evenly as rendezvous did. But adding a server to 100 moved 1.55 times the minimum with its usual 65,537-slot table, and going from 1,000 to 1,001 servers with a 100,003-slot table moved 6.6 times the minimum: 0.66% of all keys instead of 0.1%. Removing a server moved about the same. When the server list changes, the turn-taking that fills the table plays out differently from the start, so slots change hands between servers that were never involved.

A bigger table helps. For 100 servers, a 10,007-slot table moved 2.6× the minimum, 65,537 moved 1.55× and 655,373 moved 1.14×. Small tables are worse: at 1,009 slots, ten per server, a join moved 4.4× the minimum. Maglev is built for packet load balancers, where a lookup must be one memory read and a few extra moved connections are tolerable; that's a sensible trade, but it is a trade.

A load cap costs moves unless you have points

The bounded-loads ring does what it promises: the busiest server never exceeded the cap. The price shows up on change. Keys that overflow a full server spill onto its neighbors, and when the server count changes, the cap changes with it and the spills land differently. I modeled it as reassigning the keys in a fixed order after each change.

cap (× average)1 point16 points100 points
1.0512.4×2.09×1.44×
1.110.7×1.75×1.23×
1.256.9×1.24×1.05×
1.54.0×0.97×1.04×
22.2×0.90×1.04×

Keys moved when a 101st server joins, as a multiple of the minimum. 20 seeds each; values under 1 are the newcomer drawing less than its fair share. From 1.5 up with 100 points, and from 2 up with 16, the cap is never reached.

With one point per server the ring is so uneven that a tight cap is reached constantly, and a 1.25× cap moved 6.9 times the minimum. With 100 points the ring is already within about 1.27×, so the same cap almost never bites and costs 1.05×; even a 1.1× cap costs only 1.23×. Points do the bulk of the balancing, and the cap trims the tail.

What I'd use

Limits: uniform random keys with equal weights and equal-capacity servers, so this is the best case for every scheme; skewed key popularity adds its own imbalance on top. Maglev and the bounded-loads ring are my JavaScript implementations from the papers' descriptions, and the load cap is applied by reassigning in a fixed order, not to live traffic. Jump uses a different per-key random generator than the paper, with the same distribution.