{"id":"consistent-hashing","title":"Consistent hashing","sections":[{"id":"overview","title":"Overview","html":"<p>Once data or cache entries are spread across several machines, every request has to answer one question: which machine holds this key? The obvious answer, hashing the key and taking the result modulo the number of machines, works until the number of machines changes. Then almost every key moves at once. Consistent hashing is a family of techniques that keeps most keys where they are when machines join or leave.</p>\n<p>It comes up in interviews whenever you <a href=\"/learn/database#sharding\">shard</a> a database, partition a distributed cache, or design something like a <a href=\"/learn/design-key-value-store\">key-value store</a>. The primer mentions it in passing, both as a way to cut rebalancing work in sharding and for routing in the <a href=\"/learn/design-query-cache#step-4-scale-the-design\">query cache design</a>. This chapter fills in how it works and where it falls short.</p>","headings":[]},{"id":"the-problem-with-hash-mod-n","title":"The problem with hash mod N","html":"<h3 id=\"the-problem-with-hash-mod-n\">The problem with hash mod N</h3>\n<p>With N servers, the simple scheme is <code>server = hash(key) mod N</code>. A good hash spreads keys evenly and the lookup is a single division.</p>\n<p>Now add a server. A key stays put only if <code>hash(key) mod N</code> equals <code>hash(key) mod (N+1)</code>, and for a uniform hash that happens for only about 1 in N+1 keys. Going from 4 servers to 5, about 20% of keys stay where they were and <strong>80% move</strong>. Going from 10 to 11, about 91% move. Removing a server, or having one crash, is just as bad.</p>\n<p>Ideally, adding a fifth server would move only the fifth of keys that the new server should now own, and nothing else.</p>\n<p>What all that movement costs depends on what you're storing:</p>\n<ul>\n<li><strong>For a cache,</strong> moved keys become misses. If most of the cache misses at once, the load falls through to the database, which was sized assuming a high hit rate. Resizing the cache can take down the database behind it.</li>\n<li><strong>For a datastore,</strong> moved keys mean copying data between machines. Moving 80% of a large dataset takes hours and saturates the network while it runs.</li>\n</ul>","headings":[{"id":"the-problem-with-hash-mod-n","title":"The problem with hash mod N","depth":3}]},{"id":"the-ring","title":"The ring","html":"<h3 id=\"the-ring\">The ring</h3>\n<p>Consistent hashing, introduced by Karger and colleagues in 1997 for web caching (work that led to Akamai), hashes servers and keys into the same space. Picture the hash range bent into a circle. Each server sits at the position given by hashing its name, and a key belongs to the <strong>first server clockwise</strong> from the key's position.</p>\n<p>A small example makes it concrete. Use a toy ring numbered 0 to 99 (real rings use 0 to 2^64 − 1) and three servers that hash to positions 10, 45 and 80:</p>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<div class=\"table-wrap\"><table><thead><tr><th>Server</th><th>Position</th><th>Owns positions</th></tr></thead><tbody><tr><td>North</td><td>10</td><td>81–99 and 0–10 (wrapping past the top)</td></tr><tr><td>East</td><td>45</td><td>11–45</td></tr><tr><td>West</td><td>80</td><td>46–80</td></tr></tbody></table></div>\n<figure class=\"diagram\" data-text=\"North at 10 -> East at 45; East at 45 -> West at 80; West at 80 -> North at 10; key at 93 -> North at 10; key at 22 -> East at 45; key at 61 -> West at 80\"><div class=\"diagram-scroll\"><svg class=\"dg\" viewBox=\"0 0 480 318\" width=\"480\" role=\"img\" aria-label=\"A toy ring from 0 to 99. Each key goes to the first server clockwise.\"><defs><marker id=\"dg-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path class=\"dg-arrowhead\" d=\"M0,1 L9,5 L0,9 z\"></path></marker></defs><line class=\"dg-edge\" x1=\"270\" y1=\"69\" x2=\"367.8\" y2=\"157\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"329\" y1=\"186\" x2=\"153\" y2=\"186\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"110\" y1=\"159\" x2=\"207.8\" y2=\"71\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"151\" y1=\"42\" x2=\"167\" y2=\"42\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"400\" y1=\"69\" x2=\"400\" y2=\"157\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"195\" y1=\"255\" x2=\"128.3\" y2=\"215\" marker-end=\"url(#dg-arrow)\"></line><rect class=\"dg-node dg-box\" x=\"172\" y=\"18\" width=\"136\" height=\"48\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"240\" y=\"46.5\">North at 10</tspan></text><rect class=\"dg-node dg-box\" x=\"332\" y=\"162\" width=\"136\" height=\"48\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"400\" y=\"190.5\">East at 45</tspan></text><rect class=\"dg-node dg-box\" x=\"12\" y=\"162\" width=\"136\" height=\"48\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"80\" y=\"190.5\">West at 80</tspan></text><rect class=\"dg-node dg-note\" x=\"12\" y=\"18\" width=\"136\" height=\"48\" rx=\"8\"></rect><text class=\"dg-label dg-muted\" text-anchor=\"middle\"><tspan x=\"80\" y=\"46.5\">key at 93</tspan></text><rect class=\"dg-node dg-note\" x=\"332\" y=\"18\" width=\"136\" height=\"48\" rx=\"8\"></rect><text class=\"dg-label dg-muted\" text-anchor=\"middle\"><tspan x=\"400\" y=\"46.5\">key at 22</tspan></text><rect class=\"dg-node dg-note\" x=\"172\" y=\"258\" width=\"136\" height=\"48\" rx=\"8\"></rect><text class=\"dg-label dg-muted\" text-anchor=\"middle\"><tspan x=\"240\" y=\"286.5\">key at 61</tspan></text></svg></div><figcaption>A toy ring from 0 to 99. Each key goes to the first server clockwise.</figcaption></figure>\n<p>So a key hashing to 22 lives on East, 61 on West, and 93 wraps around to North. Now see what a change costs:</p>\n<ul>\n<li><strong>Add a server, South, at position 30.</strong> South takes positions 11–30, which used to belong to East. North and West don't notice. About 20% of the ring moved, all of it from one server.</li>\n<li><strong>Remove West.</strong> Its range, 46–80, passes to the next server clockwise, North. East doesn't notice.</li>\n</ul>\n<p>On average, adding or removing one of N servers moves about 1/N of the keys, which is the minimum possible.</p>\n<p>The lookup is cheap. Keep the server positions in a sorted array, binary-search for the first position greater than the key's hash, and wrap to the start if you run off the end. That's O(log N), and the array is small enough to keep on every client.</p>\n<p>The plain ring has two problems:</p>\n<ul>\n<li><strong>Uneven arcs.</strong> Random positions don't divide a circle evenly. In the toy ring, North owns 30 positions and West 35, which is close to fair only by luck. With 10 servers and one position each, the server with the largest arc owns about 29% of the ring on average, nearly three times its fair share of 10%.</li>\n<li><strong>Load dumps onto one neighbor.</strong> When West failed above, North's share jumped from 30% to 65% of the ring, while East's stayed the same. The neighbor may then overload too.</li>\n</ul>","headings":[{"id":"the-ring","title":"The ring","depth":3}]},{"id":"virtual-nodes","title":"Virtual nodes","html":"<h3 id=\"virtual-nodes\">Virtual nodes</h3>\n<p>The fix for both problems is to give each physical server many positions on the ring, called <strong>virtual nodes</strong> or tokens. Hash <code>\"A#0\"</code>, <code>\"A#1\"</code>, … <code>\"A#99\"</code> instead of just <code>\"A\"</code>, and every one of those positions points back to server A.</p>\n<ul>\n<li><strong>Balance improves</strong> with more positions. The spread between servers shrinks roughly as 1/√V for V virtual nodes each. In a quick simulation of 10 servers, one position each gave a standard deviation of about 86% of the fair share; 100 positions gave about 9%; 1,000 gave about 3%.</li>\n<li><strong>Failure spreads.</strong> A failed server's 100 small ranges go to many different successors, so no single neighbor takes the whole load.</li>\n<li><strong>Unequal hardware is easy.</strong> Give a machine with twice the memory twice the virtual nodes. Memcached's libketama client does this, placing 160 points per server, scaled by each server's weight.</li>\n</ul>\n<div class=\"widget-slot\" data-widget=\"hash-ring\"></div>\n<p>Here is the whole idea in Python, including the replica walk explained in the next section:</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> bisect, hashlib</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> h</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(s: </span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">str</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">) -> </span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">int</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    return</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> int</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.from_bytes(hashlib.md5(s.encode()).digest()[:</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">8</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">], </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"big\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">class</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Ring</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> __init__</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, nodes, vnodes</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">100</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">):</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.points </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> sorted</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">((h(</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">f</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">{</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">n</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">}</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">#</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">{</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">i</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">}</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">), n) </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">for</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> n </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">in</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> nodes </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">for</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> i </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">in</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> range</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(vnodes))</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.hashes </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> [p </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">for</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> p, _ </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">in</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.points]</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> owners</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, key: </span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">str</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, replicas: </span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">int</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 1</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">) -> list[</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">str</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">]:</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        start </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> bisect.bisect_right(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.hashes, h(key))  </span><span style=\"color:#6A737D;--shiki-dark:#6A737D\"># first point clockwise</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        found </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> []</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        for</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> j </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">in</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> range</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">len</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.points)):                 </span><span style=\"color:#6A737D;--shiki-dark:#6A737D\"># walk on, wrapping around</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">            node </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.points[(start </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">+</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> j) </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">%</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> len</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.points)][</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">1</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">]</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">            if</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> node </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">not</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> in</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> found:                         </span><span style=\"color:#6A737D;--shiki-dark:#6A737D\"># skip a node's other vnodes</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">                found.append(node)</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">                if</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> len</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(found) </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">==</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> replicas:</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">                    break</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        return</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> found</span></span></code></pre>\n<p>Virtual nodes have costs too. The ring table grows to N × V entries, and every virtual node is a separate range to stream, repair and track. Cassandra used 256 tokens per node by default for years; version 4.0 lowered the default to 16 and paired it with an allocation algorithm that picks token positions for balance instead of at random, because hundreds of tokens per node made repair and streaming slower. The Dynamo paper describes a similar journey: Amazon moved from random tokens to splitting the ring into a fixed number of equal partitions and handing those out to nodes, which made balance predictable and data easier to move.</p>","headings":[{"id":"virtual-nodes","title":"Virtual nodes","depth":3}]},{"id":"replication-along-the-ring","title":"Replication along the ring","html":"<h3 id=\"replication-along-the-ring\">Replication along the ring</h3>\n<p>The ring also tells you where to put copies. To store three copies of a key, start at the key's position and walk clockwise, taking the first three <strong>distinct physical servers</strong> you meet. Dynamo calls this list the key's <strong>preference list</strong>.</p>\n<p>The word \"distinct\" matters. With virtual nodes, the next few positions clockwise might all belong to the same machine, and three copies on one machine protect against nothing. The walk skips positions whose physical server is already in the list, which is what the <code>if node not in found</code> line above does. Real systems go further and spread replicas across racks or data centers; Cassandra's <code>NetworkTopologyStrategy</code> does this.</p>\n<p>Replication along the ring pairs naturally with quorum reads and writes (read and write quorums that overlap), with read repair, and with Dynamo-style <strong>hinted handoff</strong>, where a write meant for a down replica is held by the next server on the ring until the replica returns. Those belong to <a href=\"/learn/availability-patterns#replication\">replication</a> and <a href=\"/learn/consistency-patterns#eventual-consistency\">consistency</a> more than to hashing; <a href=\"/learn/design-key-value-store\">design a key-value store</a> puts them together.</p>","headings":[{"id":"replication-along-the-ring","title":"Replication along the ring","depth":3}]},{"id":"hot-keys","title":"Hot keys","html":"<h3 id=\"hot-keys\">Hot keys</h3>\n<p>Consistent hashing balances <strong>keys</strong>, not <strong>load</strong>. If one key gets a hundred times more traffic than the rest, such as a celebrity's profile or a flash-sale product, it lands on one server no matter how many virtual nodes you add. More nodes don't help either, because the hot key still maps to exactly one place.</p>\n<p>Common fixes, each with a cost:</p>\n<ul>\n<li><strong>Cache hot reads closer to the caller.</strong> A small in-process cache on each application server absorbs the hottest reads before they reach the shared tier. The cost is staleness for as long as the local entry lives.</li>\n<li><strong>Read from any replica.</strong> If the key is already stored on several servers, spread its reads across all of them instead of always hitting the first. That works for read-heavy hot keys, but it only spreads load as many ways as there are replicas.</li>\n<li><strong>Split the key.</strong> Write to <code>counter:42#0</code> through <code>counter:42#9</code>, which hash to different servers, and sum them on read. This suits counters and append-only data. Reads get more expensive, and you need to know in advance which keys are hot.</li>\n<li><strong>Bound the load.</strong> \"Consistent hashing with bounded loads\" (Mirrokni, Thorup and Zadimoghaddam, 2016) caps each server at a set factor above the average load. When the owner is full, a request moves on to the next server clockwise. HAProxy supports this through its <code>hash-balance-factor</code> setting. It suits load balancing, where any server can handle any request but you'd prefer the one with a warm cache.</li>\n</ul>","headings":[{"id":"hot-keys","title":"Hot keys","depth":3}]},{"id":"alternatives-and-a-comparison","title":"Alternatives and a comparison","html":"<h3 id=\"alternatives-and-a-comparison\">Alternatives and a comparison</h3>\n<p>The ring isn't the only way to get minimal movement.</p>\n<p><strong>Rendezvous hashing</strong>, also called highest random weight hashing (Thaler and Ravishankar, 1996), skips the ring. For each key, compute <code>hash(key, server)</code> for every server and pick the server with the highest score. Removing a server only moves the keys for which it scored highest. Balance is good without virtual nodes, and taking the top few scores gives you replica placement for free. The catch is that every lookup touches every server: O(N) work. That's fine for tens of servers and wasteful for thousands.</p>\n<p><strong>Jump consistent hash</strong> (Lamping and Veach, Google, 2014) is a few lines of arithmetic that map a 64-bit key and a bucket count N to a bucket number from 0 to N−1. It needs no memory, runs in O(log N) time and balances almost perfectly. The limitation is that buckets are numbered: you can grow or shrink only at the end. If bucket 3 of 10 disappears, you can't simply remove it. That suits storage where each \"bucket\" is a logical shard with its own replicas, so a machine failure is handled below the hashing layer. It doesn't suit a pool of cache servers where any one of them can die.</p>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<div class=\"table-wrap\"><table><thead><tr><th>Method</th><th>Lookup cost</th><th>State to keep</th><th>Balance</th><th>Remove any server?</th><th>Good fit</th></tr></thead><tbody><tr><td>hash mod N</td><td>O(1)</td><td>none</td><td>good</td><td>moves almost all keys</td><td>fixed-size clusters</td></tr><tr><td>Ring + virtual nodes</td><td>O(log NV)</td><td>N × V positions</td><td>good with enough V</td><td>yes</td><td>Dynamo-style stores, cache clients</td></tr><tr><td>Rendezvous</td><td>O(N)</td><td>server list</td><td>good</td><td>yes</td><td>small server sets, picking replicas</td></tr><tr><td>Jump hash</td><td>O(log N)</td><td>none</td><td>near perfect</td><td>only the last bucket</td><td>numbered, replicated shards</td></tr></tbody></table></div>\n<p>Who uses what:</p>\n<ul>\n<li><strong>Amazon's Dynamo</strong> (2007) built its partitioning and replication on a ring with virtual nodes. <strong>Cassandra</strong> inherited the design, with Murmur3 tokens and configurable vnodes per node. Riak uses a ring of fixed partitions.</li>\n<li><strong>Memcached</strong> servers don't know about each other. The client chooses the server, and libketama (from Last.fm) made ring-based consistent hashing the standard way to do it. Clients such as libmemcached offer a ketama mode.</li>\n<li><strong>Load balancers</strong> use it to keep a client or key on the same backend for cache warmth. NGINX's <code>hash … consistent</code> directive uses ketama, and HAProxy has <code>hash-type consistent</code>.</li>\n</ul>","headings":[{"id":"alternatives-and-a-comparison","title":"Alternatives and a comparison","depth":3}]},{"id":"check-yourself","title":"Check yourself","html":"<h3 id=\"check-yourself\">Check yourself</h3>\n<details>\n<summary>A cache tier of 10 servers uses hash mod N. You add an eleventh during peak traffic. Roughly what happens to the hit rate and to the database?</summary>\n<p>About 10 of every 11 keys now map to a different server, so roughly 91% of lookups miss at once, and the hit rate stays low until the caches refill. Nearly all of that traffic falls through to the database, which was sized for the old hit rate and may fall over. With a consistent hashing ring, only about 1/11 of keys would move.</p>\n</details>\n<details>\n<summary>A ring has three physical servers with one position each. One fails. Where does its load go, and how would virtual nodes change that?</summary>\n<p>All of its range goes to the next server clockwise, which suddenly carries roughly two-thirds of the traffic while the third server's load doesn't change. With 100 virtual nodes per server, the failed server's ranges are scattered around the ring, so its load splits roughly evenly between the two survivors.</p>\n</details>\n<details>\n<summary>Why does the replica walk need to skip positions belonging to a physical server that's already been chosen?</summary>\n<p>With virtual nodes, consecutive ring positions can belong to the same machine. Without the skip, \"three replicas\" could mean three copies on one server, and losing that server would lose the data. Skipping repeats guarantees distinct machines. Production systems also skip to spread across racks or zones.</p>\n</details>\n<details>\n<summary>One product page key gets 50 times the traffic of any other. Will adding virtual nodes or more servers help? What would you do?</summary>\n<p>No. Both rebalance how keys are spread, but this one key still maps to exactly one server. Put a short-lived in-process cache in front of the shared tier so most reads never leave the application server, or spread reads across the key's replicas. If the traffic is writes, such as a view counter, split the key into several sub-keys and sum them on read.</p>\n</details>\n<details>\n<summary>When would you pick jump consistent hash over a ring?</summary>\n<p>When your buckets are logical shards numbered 0 to N−1 that never vanish individually, because each shard is itself replicated, and you only grow by adding shards at the end. Then jump hash gives near-perfect balance with no table to store or sync. For a pool of cache servers where any one can fail, use a ring or rendezvous hashing instead.</p>\n</details>\n<h3 id=\"further-reading\">Further reading</h3>\n<ul>\n<li><a href=\"https://doi.org/10.1145/258533.258660\" target=\"_blank\" rel=\"noopener noreferrer\">Karger et al., \"Consistent Hashing and Random Trees\" (STOC 1997)</a>: the original paper.</li>\n<li><a href=\"https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf\" target=\"_blank\" rel=\"noopener noreferrer\">DeCandia et al., \"Dynamo: Amazon's Highly Available Key-value Store\" (SOSP 2007)</a>: the partitioning and replication sections cover the ring, virtual nodes and preference lists, and the lessons-learned section compares partitioning strategies.</li>\n<li><a href=\"https://cassandra.apache.org/doc/latest/cassandra/architecture/dynamo.html\" target=\"_blank\" rel=\"noopener noreferrer\">Apache Cassandra: Dynamo architecture</a>: how tokens, vnodes and replica placement work in practice.</li>\n<li><a href=\"https://arxiv.org/abs/1406.2294\" target=\"_blank\" rel=\"noopener noreferrer\">Lamping and Veach, \"A Fast, Minimal Memory, Consistent Hash Algorithm\"</a>: the jump hash paper, with the full code.</li>\n<li><a href=\"https://arxiv.org/abs/1608.01350\" target=\"_blank\" rel=\"noopener noreferrer\">Mirrokni, Thorup and Zadimoghaddam, \"Consistent Hashing with Bounded Loads\"</a>: capping per-server load.</li>\n<li><a href=\"https://en.wikipedia.org/wiki/Rendezvous_hashing\" target=\"_blank\" rel=\"noopener noreferrer\">Rendezvous hashing (Wikipedia)</a>: a clear overview with references to the original papers.</li>\n<li><a href=\"https://github.com/RJ/ketama\" target=\"_blank\" rel=\"noopener noreferrer\">libketama source</a>: the Memcached client library that made ring hashing standard.</li>\n<li>Alex Xu, <em>System Design Interview – An Insider's Guide</em> (2020), \"Design consistent hashing\".</li>\n<li><a href=\"https://systemdesignschool.io/primer\" target=\"_blank\" rel=\"noopener noreferrer\">System Design School primer</a>: \"Partitioning\", under Designing for Scale.</li>\n</ul>","headings":[{"id":"check-yourself","title":"Check yourself","depth":3},{"id":"further-reading","title":"Further reading","depth":3}]}],"sourceFiles":[]}