Consistent Hashing & Designing a Key-Value Store

A system-design interview guide to the one idea that makes distributed storage tractable: how to spread keys across a changing set of servers without reshuffling everything, and how that single technique becomes the backbone of a partitioned, replicated, tunable key-value store.

Student edition · start with the guarantee

Spread a key-value store across changing servers without moving nearly every key, then reason separately about replication and read correctness.

Correctness contract: Placement is deterministic for a shared membership version; replicas are distinct physical failure domains. Acknowledgement and read-freshness policies are explicit.

Basic HTTP, databases, and programming are assumed. Specialized terms are explained below. Named products motivate the problem; this is an educational design, not a claim about their current internals. Capacity numbers are illustrative decimal payload estimates, not benchmarks. Add indexes, protocol overhead, replicas, headroom, and failure capacity before provisioning.

Almost every distributed data system has to answer the same question first: given a key, which machine holds it? When there is exactly one machine the answer is trivial, but the moment you have several — and especially when that number changes as machines come and go — the mapping becomes the whole game. Consistent hashing is the technique that keeps that mapping stable under change, and it turns out to be the foundation that the rest of a key-value store is built on. This guide starts from the naive approach, shows why it falls apart, develops consistent hashing and virtual nodes, and then uses it as the partitioning layer of a complete distributed key-value store — replication, the CAP tradeoff, tunable quorums, conflict resolution, and the background machinery that keeps replicas honest.

Contents

  1. The Problem Setting: A Distributed Key-Value Store
  2. Back-of-the-Envelope Estimation
  3. API and Data Model
  4. The Problem with Naive Hashing
  5. Consistent Hashing
  6. Virtual Nodes
  7. Replication
  8. The CAP Theorem
  9. Tunable Consistency: N, W, R
  10. Versioning & Conflict Resolution
  11. Anti-Entropy with Merkle Trees
  12. Failure Handling
  13. Tradeoffs and Alternatives
  14. A 45-Minute Interview Walkthrough
  15. Common Mistakes
  16. Summary
  17. Check Yourself
  18. Key Terms
  19. Further Reading
  20. Concepts to understand first
  21. Worked example
  22. Runnable Python model

Concepts to understand first

Each definition includes a concrete example and the condition under which the mechanism is useful. You can read this guide without opening another guide.

Consistent hashing

A placement algorithm that maps both keys and server tokens into a shared, ordered hash space. A key belongs to the first token clockwise at or after its hash; the end wraps to the start. Adding a token changes only its newly owned interval, rather than most keys.

Example. Tokens at 10, 40, 80 own positions (80,10], (10,40], and (40,80]. Adding token 25 moves positions (10,25] from token 40 to token 25.

Limit. It stabilizes placement, not replica consistency. Expected primary remapping when adding an equally weighted node is 1/(n+1); balance depends on token allocation and workload.

Virtual nodes

Multiple tokens assigned to one physical server. Many smaller owned intervals can average out random placement imbalance and let a larger server receive more tokens.

Example. Server A owns tokens 10 and 70; B owns 30 and 90. A primary and its replicas must be selected from distinct physical servers, not merely distinct tokens.

Limit. More tokens cannot split an indivisible hot key. Replication also needs independent hosts, racks, or zones; token count alone does not create fault tolerance.

Quorum

A set of replicas whose responses are sufficient for an operation. In a fixed set of N replicas, read size R and write size W overlap whenever R+W>N. A majority is one particular quorum, not the definition of every quorum.

Example. With N=3, writes to {A,B} and reads from {B,C} share B. If the write completed durably and replicas preserve a comparable version, B carries that version forward.

Limit. Overlap alone does not provide linearizability: concurrent or failed writes, sloppy replica sets, and version selection matter. Consensus adds protocol rules to preserve decisions.

Linearizability

Each completed operation appears to occur at one instant between its invocation and response, respecting real-time order. A read that begins after a write finishes cannot return a value older than that write, unless a later write superseded it.

Example. A write x=7 returns success; a later read must return 7 or a value from a later ordered write. A read overlapping that write may return the old or new value.

Limit. Not the same as durability or transaction serializability. A leader may be stale; linearizable leader reads need current authority and an applied-state barrier.

Vector clocks

A version tracks one logical counter per participant. Version A causally dominates B when every counter in A is at least B's and at least one is greater. If neither dominates, the versions are concurrent; identical vectors are equal.

Example. {A:2,B:1} follows {A:1,B:1}. But {A:2,B:1} and {A:1,B:2} are concurrent and need an application merge or explicit conflict handling.

Limit. Clock size grows with participants; pruning can lose causal information. Wall-clock last-write-wins selects one value, but may discard a valid concurrent update.

Merkle trees

A tree of hashes used to compare large datasets cheaply. Leaves hash canonical key/value/version data for agreed ranges; parents hash their children. A differing root directs comparison into the branches that differ.

Example. Two replicas compare a range root, then descend only into the differing half instead of transferring every key.

Limit. Matching hashes imply matching encoded content only assuming negligible collisions. Trees need compatible ranges/encoding and a consistent view while writes continue.

Tombstones

A durable delete marker that carries a version. Replicas merge it like a write so an old live value is not treated as a new record during repair.

Example. A deletes key k while B is offline. A later repair sends the versioned delete to B, removing B's old value.

Limit. Collecting the marker too early can resurrect data. Safe retention depends on repair completion, replica return policies, and the maximum tolerated offline period.

1. The Problem Setting: A Distributed Key-Value Store

Consistent hashing is usually asked inside a bigger question: design a distributed key-value store (think Amazon Dynamo, Apache Cassandra or Riak). Start with the requirements, because they explain why each later mechanism exists.

FunctionalNon-functional
put(key, value) and get(key)Scale out: add machines to add capacity, with little data movement.
Values are small (under about 10 KB)High availability: keep serving through machine and network failures.
Keys spread across many machines automaticallyTunable consistency: let each use case pick speed or freshness.
Survive the loss of any single machineLow latency: single-digit milliseconds for reads and writes.

Clarifying questions worth asking: How big is the data? What is the read/write ratio? Is stale data ever acceptable, and for how long? Must a write never be lost? Single data center or several?

2. Back-of-the-Envelope Estimation

Practice numbers (assumptions): 10 billion keys, 1 KB average value, replication factor N = 3, 100,000 reads/s and 20,000 writes/s at peak. Each node has 2 TB of disk and we fill it to 70%.

QuantityWorkingResult
Raw data10 × 109 keys × 1 KB10 TB
With 3 replicas10 TB × 330 TB
Usable per node2 TB × 0.71.4 TB
Nodes for storage30 ÷ 1.4 = 21.4, round up and add headroom~24 nodes
Replica reads/seach read goes to all N = 3 replicas: 100k × 3300k/s (~12.5k per node)
Replica writes/seach write goes to all N = 3 replicas: 20k × 360k/s (~2.5k per node)
Primary ownership changed on adding node 25Expected ~1/25 of 10 TB logical primary payload under equally weighted placement. Replicated-copy migration depends on replica placement and the migration protocol.~0.4 TB primary; measure replica traffic separately.
What the numbers tell you

Per-node request averages are only planning inputs. Benchmark CPU, tail latency, hot keys, repair/rebalance traffic, and survivor capacity before calling the load modest.

3. API and Data Model

get(key)                  -> (value(s), context)   # may return several siblings on conflict
put(key, context, value)  -> ok                    # context carries the version (vector clock)
delete(key, context)      -> ok                    # usually writes a tombstone

Deletes write versioned tombstones so replicas can learn the deletion. Safe garbage collection must wait for the required repair/offline horizon; collecting too early can resurrect an old value.

4. The Problem with Naive Hashing

Suppose you have N servers and you want to spread keys evenly across them. The obvious scheme is to hash the key to an integer and take it modulo the server count:

server_index = hash(key) % N

While N stays fixed this works beautifully. The hash spreads keys uniformly, the modulo folds them into the range 0 .. N-1, and every server gets roughly the same share. The trouble is the N in that formula. The instant the server count changes — a machine is added to grow capacity, or one fails and drops out — N becomes a different number, and the modulo of nearly every key changes with it.

Concretely, suppose you have 4 servers and add a 5th. A key that hashed to 1000 maps to 1000 % 4 = 0 before and 1000 % 5 = 0 after, so it happens to stay. But 1005 moves from server 1 to server 0, 1006 moves from 2 to 1, and 1004 moves from 0 to 4. A key stays put only when h % N == h % (N+1), which happens for just 1/(N+1) of hash values. So going from N to N+1 servers remaps about N/(N+1) of all keys. With 10 servers growing to 11, a simulation over 100,000 keys moved 90.8% of them (the formula says 10/11 = 90.9%). Almost everything moves.

SymptomWhy it hurts
Cache miss stormIf the servers are a cache, nearly every key now resolves to a server that does not hold it. The cache effectively empties at once and every request falls through to the backing store.
Massive data movementIf the servers are a database, the data for the remapped keys must physically migrate to its new owner. Adding one node triggers a near-total reshuffle of the cluster.
Load spike during reshuffleThe migration and the cold cache happen exactly when you were trying to add capacity, so the change that should help can instead tip the system over.
The deeper problem is that hash(key) % N couples the key-to-server mapping to the total count of servers. Any change to the count perturbs the mapping for keys that had nothing to do with the server that changed. We want a scheme where adding or removing one server only affects the keys that server is directly responsible for.

5. Consistent Hashing

Hash tokens and keys into a compatible common ordered space. Their hash algorithms need not literally be identical, but every participant must use the same placement rules and membership version. Sort tokens; a key belongs to the first token at or clockwise after its hash, wrapping to the first token if necessary. A token at t owns the interval (previous counterclockwise token, t].

from bisect import bisect_left

def owner(tokens, owners, position):
    if not tokens or len(tokens) != len(owners):
        raise ValueError('nonempty, aligned ring required')
    # Precondition: tokens are unique and sorted; position is in their hash space.
    return owners[bisect_left(tokens, position) % len(tokens)]

Adding a token transfers its new owned interval from its clockwise successor. With equally weighted physical nodes and good token allocation, adding a node to n nodes moves about 1/(n+1) of primary ownership in expectation; removing one moves about 1/n. Real token intervals, weights, replica placement, and migration state determine actual bytes transferred.

A transient suspected failure does not necessarily remove a member from the ring. Separate stable membership/rebalancing from temporary replica routing, or brief outages can trigger unnecessary mass migrations.

6. Virtual Nodes

Plain consistent hashing has a weakness: with only one point per server, the arcs between servers are uneven. A server that happens to land far from its clockwise predecessor owns a large arc and a heavy share of keys; one that lands in a crowded stretch owns almost nothing. With a handful of servers this variance can be severe, and it gets worse when a server leaves — its entire arc dumps onto a single neighbor, which can then become a hotspot.

Virtual nodes fix this by giving each physical server many points on the ring instead of one. A server s0 is hashed under several derived labels — s0_0, s0_1, s0_2, and so on — and each label lands at its own position. A key still belongs to the first point clockwise, but that point now maps back to whichever physical server owns it.

function build_ring(servers, vnodes_per_server):
  ring = empty()
  for s in servers:
    for i in range(vnodes_per_server):
      ring.insert(hash(f"{s.id}_{i}"), s)  # many points, one server
  return ring                              # lookup() is unchanged

You can see the effect with a short simulation: 10 servers, 100,000 keys, MD5 as the hash, and a varying number of virtual nodes per server.

import bisect, hashlib

def h(text):
    return int.from_bytes(hashlib.sha256(text.encode()).digest()[:8], "big")

def build(servers, v):
    if not servers or len(servers) != len(set(servers)) or type(v) is not int or v <= 0:
        raise ValueError("unique nonempty servers and positive vnode count required")
    ring = sorted((h(f"{server}#{i}"), server) for server in servers for i in range(v))
    if len({point for point, _ in ring}) != len(ring):
        raise ValueError("token collision: choose a deterministic collision policy")
    return [p for p, _ in ring], [server for _, server in ring]

def owner(points, owners, key):
    if not points or len(points) != len(owners):
        raise ValueError("nonempty aligned ring required")
    return owners[bisect.bisect_left(points, h(key)) % len(points)]
Virtual nodes per serverBusiest server vs averageSpread (std. dev. of load)
12.51×83.5%
101.46×24.7%
1001.22×15.3%
1,0001.03×2.1%

With one point per server, the busiest server carried two and a half times its fair share. The spread shrinks roughly like 1/√v as you add virtual nodes v. (Your exact numbers will vary with the hash and key set.) For reference, Apache Cassandra used 256 tokens per node by default for years, and lowered the default to 16 in version 4.0 with a smarter token allocator.

Virtual nodes are a knob, not a free lunch: more points per server means a larger ring to maintain and search, and slightly more metadata to gossip around. In practice a few dozen to a few hundred virtual nodes per server gives load variance low enough that no single node becomes a bottleneck.

7. Replication

Up to here a key has exactly one owner, so a single server failure loses data and stalls every request for the keys it held. A real key-value store keeps each key on more than one server. The ring makes the replica set fall out naturally: store each key on the first N servers you meet walking clockwise from the key's position (its owner plus the next N-1), where N is the replication factor.

The one subtlety is virtual nodes. Because a single physical server appears at many points on the ring, naively taking the next N points clockwise might land on several virtual nodes that all belong to the same physical machine — which would defeat the purpose of replicating. So when collecting the replica set you walk clockwise but skip points that map to a physical server already chosen, until you have N distinct physical servers. These N servers are often called the key's preference list.

function replica_set(key, ring, N):
  pos = hash(key)
  chosen = []
  for vnode in ring.points_clockwise_from(pos):
    server = ring.server_at(vnode)
    if server not in chosen:               # skip virtual duplicates
      chosen.append(server)
    if len(chosen) == N:
      break
  return chosen                            # the preference list

N distinct durable copies can retain a key after at most N−1 copy losses, assuming a surviving valid copy and a recovery protocol. That does not guarantee read/write availability: required R/W thresholds may no longer be reachable. Prefer independent failure domains, and reject a replica-set request that exceeds available distinct owners.

8. The CAP Theorem

CAP concerns a service's behavior during a partition, when messages between groups can be lost or delayed indefinitely. C means linearizability: operations behave like one copy and respect real-time order. A means every eligible request to a nonfailed node completes successfully, not merely that it returns an error quickly. You cannot promise both across the partition.

A quorum-based authority can continue on the majority side and reject or block operations on the minority. A locally writable mergeable store can remain available with weaker ordering. Neither choice alone defines durability: an AP event log can preserve both concurrent records, and a CP system can still lose data if its acknowledged state was not durable.

P is a failure condition the model permits, not a third feature to switch off. Outside partitions, coordination still trades latency against stronger ordering (the PACELC observation).

9. Tunable Consistency: N, W, R

Let N be the fixed set of replicas for a key, W the durable responses required for a completed write, and R the responses used by a read. R + W > N guarantees each such read set intersects that write set. 2W > N makes write quorums overlap. For N=3 and R=W=2, one unavailable replica still permits both operations.

Overlap is useful only if a replica in the intersection retains the relevant version and the read can identify it. It is not automatically linearizability. A read may observe an in-progress write that a later read misses; a failed write can leave partial state; sloppy quorums use stand-ins outside N; wall-clock LWW can discard a later logical update. Vector clocks expose concurrent siblings rather than defining one globally latest value.

N,W,RUseful propertyNot guaranteed by the numbers
3,2,2Fixed read/write and write/write overlap; one replica can be unavailable.Linearizability without a correct register/consensus protocol.
3,1,1Low response threshold.Overlap, newest-value reads, or survival of loss of the sole acknowledged copy.
3,3,1A completed write reached all three replicas.Write availability after any replica becomes unreachable, or safe arbitrary concurrency.

A linearizable atomic-register algorithm such as ABD uses versioned quorum reads and a write-back phase before the read returns. A consensus log instead orders committed commands, and linearizable reads establish current authority and wait for applied state. Do not label a Dynamo-style overlap rule as a consensus implementation.

10. Versioning & Conflict Resolution

Quorums tell you how many replicas to wait for, but not how to decide which value is "newest" when two writes happened without seeing each other — for example on opposite sides of a partition, or with W < N. The store needs a way to attach a version to each value and to detect when two versions are concurrent rather than one being a clean successor of the other.

ApproachHow it worksTradeoff
Vector clocksEach value carries a vector of (node, counter) pairs. A node bumps its own counter on every write it coordinates. Comparing two vectors tells you whether one strictly descends from the other (keep the descendant) or whether they are concurrent (a genuine conflict).Precisely detects concurrent writes without losing data, but the store must surface conflicts and let the client (or an application rule) merge them. Vectors can grow and need pruning.
Last-write-wins (LWW)Tag each write with a timestamp; on conflict, simply keep the one with the larger timestamp and discard the other.Trivially simple and conflict-free to resolve, but silently drops a concurrent write and depends on synchronized clocks, so it can lose data under skew.

The difference is what they do with a true conflict. Vector clocks detect it and preserve both versions (called siblings) so nothing is lost until something merges them — a shopping cart, for instance, can union the two carts. Last-write-wins resolves it immediately by picking a winner, which is fine when losing one of two concurrent updates is acceptable and is far easier to operate.

function compare(v_a, v_b):  # illustrative pseudocode
  if v_a == v_b: return EQUAL
  if all(a >= b) and any(a > b): return KEEP_A
  if all(b >= a) and any(b > a): return KEEP_B
  return CONCURRENT  # retain siblings or application-merge

function resolve_lww(a, b):
  # Total deterministic tie-break; selects one, does not preserve both.
  return max([a, b], key=(wall_timestamp, stable_writer_id, operation_id))

11. Anti-Entropy with Merkle Trees

Replicas drift apart over time — a write that only reached W of N nodes, a node that was down during an update, a dropped message. The store needs a background process to find and repair these divergences without comparing every key on every replica, which would be hopelessly expensive for large datasets. This reconciliation is called anti-entropy, and the efficient tool for it is the Merkle tree.

Merkle roots compare canonical key/value/version encodings over compatible ranges and a consistent view. Matching roots imply matching encoded content only under a negligible-collision assumption. Differing branches identify candidates for version-aware repair; concurrent writes and tombstone retention must be handled.

The win is the amount of data exchanged. To find the differences between two replicas you transfer and compare only hashes along the divergent paths. When only a few buckets differ, that is about (number of differing buckets) × (tree depth) hashes, and tree depth is logarithmic in the number of buckets. That is far less than shipping every key across to compare. (If almost everything differs, the tree saves nothing and you end up comparing most of it.) Once the differing buckets are pinpointed, only those keys are exchanged and repaired.

Anti-entropy with Merkle trees is the slow, always-on cleanup that backstops the fast path. Read-repair (fixing stale replicas noticed during a read) handles hot keys immediately; Merkle-tree sync catches the cold keys that no one has read recently.

12. Failure Handling

The final pieces keep the cluster coherent as nodes come and go. Two lightweight mechanisms cover the common cases.

function gossip_round(self, peers):
  peer = random.choice(peers)
  merge(self.membership, peer.membership)  # exchange + reconcile views

function write_with_handoff(key, value, replicas):
  for r in replicas:
    if r.is_up():
      r.store(key, value)
    else:
      backup = pick_healthy_node()
      backup.store_hint(key, value, intended=r)  # replay when r returns

13. Tradeoffs and Alternatives

The ring is not the only way to map keys to nodes. Know at least these, and when each fits.

SchemeHow it worksProsConsSeen in
Modulo hash % NDirect formulaTrivial; perfectly evenResizing moves ~all keysFixed-size shard sets
Consistent hashing ring (+ vnodes)First point clockwiseMoves ~1/N keys; supports weightsNeeds many vnodes for balance; ring metadataDynamo, Cassandra, Riak
Fixed hash slotsHash to a fixed number of slots; a table maps slots to nodesRebalancing is just moving whole slots; simple to reason aboutNeeds a slot-to-node table kept in syncRedis Cluster (16,384 slots), many sharded databases
Rendezvous (highest random weight)For each key, score every node with hash(key, node); pick the highestVery even; no ring; moves ~1/N keysO(N) per lookupSome CDNs and load balancers
Jump consistent hashA tiny arithmetic function maps a key to a bucket 0..N-1No memory; very even; fastBuckets must be numbered; can only add or remove the last bucketSharded storage with numbered shards

Worked example: adding one token and reading replicas

Use a toy ring numbered 0–99. These are hash positions, not literal key names.

Worked example, in orderStart with tokens 10, 40, 80: Key positions 5,20,35,70,90 belong to tokens 10,40,40,80,10 respectively: first token ≥ position, wrapping at 100.. Add token 25: Only interval (10,25] moves from token 40 to token 25. Of our five sample keys, only position 20 moves. Sample movement is not the expected movement over uniform random keys.. Replicate on three distinct servers: For key 20, primary placement is now 25. The next distinct physical owners are replicas; two virtual tokens on one machine do not provide two copies that survive a machine failure.. Read an overlapping quorum: A completed write on A,B overlaps a read on B,C. B can supply that version, but version selection and concurrent-write behavior still determine whether reads are linearizable.1Start with tokens 10, 40, 802Add token 253Replicate on three distinct servers4Read an overlapping quorum
Read top to bottom. The table below explains the state and guarantee at each step.
StepState, calculation, and reasoning
1. Start with tokens 10, 40, 80Key positions 5,20,35,70,90 belong to tokens 10,40,40,80,10 respectively: first token ≥ position, wrapping at 100.
2. Add token 25Only interval (10,25] moves from token 40 to token 25. Of our five sample keys, only position 20 moves. Sample movement is not the expected movement over uniform random keys.
3. Replicate on three distinct serversFor key 20, primary placement is now 25. The next distinct physical owners are replicas; two virtual tokens on one machine do not provide two copies that survive a machine failure.
4. Read an overlapping quorumA completed write on A,B overlaps a read on B,C. B can supply that version, but version selection and concurrent-write behavior still determine whether reads are linearizable.
Check your understanding: Does R+W>N alone make every read linearizable?

No. It proves set overlap in the fixed replica set, not an atomic register protocol. Concurrent/partial writes and stale versions need additional rules.

Run a small, testable model

Download concepts.py. It uses only the Python 3.10+ standard library. From the directory where you saved it, run:

python3 concepts.py hashing
python3 concepts.py test

The model checks clockwise ownership at exact token boundaries and demonstrates that only the inserted token's interval changes.

The program contains deterministic assertions and failure traces. It is a learning model, not a production service: it does not implement network consensus, distributed durability, authentication, or third-party delivery. Read its contracts and try changing an input before extending it.

Primary references for these concepts

14. A 45-Minute Interview Walkthrough

TimeWhat to do
0–5 minClarify: data size, read/write mix, consistency needs, single or multi data center.
5–10 minEstimate: raw and replicated storage, node count, per-node load.
10–20 minPartitioning: show why modulo fails, draw the ring, add virtual nodes.
20–30 minReplication and consistency: preference list, CAP choice, pick N/W/R for the use case.
30–40 minConflicts and repair: vector clocks or LWW, read repair, Merkle trees.
40–45 minFailures: gossip, hinted handoff, what happens when a node dies for good.

15. Common Mistakes

16. Summary

A distributed key-value store is consistent hashing plus a series of deliberate tradeoffs layered on top of it:

ConcernMechanism
Which server holds a key?Hash keys and servers onto one ring; a key belongs to the first server clockwise.
Why not hash(key) % N?For equally weighted placement, adding one node to n changes about 1/(n+1) of primary ownership in expectation; removing one changes about 1/n. Replica-copy traffic and real balance depend on the placement/migration policy.
How is load kept even?Virtual nodes: each physical server occupies many ring points, smoothing variance and spreading the load of a departed node.
How does data survive failure?Replicate each key onto the first N distinct physical servers clockwise (the preference list).
What gives during a partition?CAP forces a choice: CP refuses to serve stale data; AP stays available and reconciles later.
How is the tradeoff tuned?R+W>N guarantees overlap of fixed replica sets. Linearizable effects require a correct version/concurrency/register or consensus protocol, not just that inequality.
How are conflicts resolved?Vector clocks detect concurrent writes and keep siblings; last-write-wins picks a timestamp winner more simply.
How do replicas stay in sync?Anti-entropy with Merkle trees finds divergent buckets cheaply; read-repair fixes hot keys inline.
How is membership and transient failure handled?Gossip spreads the cluster view; hinted handoff keeps writes available through short outages.
The recurring theme: consistent hashing decides where data lives so cheaply that everything else — replication, quorums, conflict resolution, repair — can be expressed in terms of the ring. Get the placement layer right and the rest of the store becomes a set of tunable policies rather than rewrites.

17. Check Yourself

  1. A cache cluster grows from 9 to 10 servers using hash % N. Roughly what fraction of keys now miss?
    Answer

    About 9/10 = 90%. Only keys where h % 9 == h % 10 stay, which is 1/10 of them.

  2. Same growth with a consistent hashing ring. About what fraction of keys move?
    Answer

    About 1/10. Only the keys on the arc the new server takes over move, and they all come from its neighbors.

  3. Why can't the replica set just be “the next 3 points on the ring” when you use virtual nodes?
    Answer

    Several consecutive points may belong to the same physical machine. Losing that machine would lose all “replicas” at once. Skip points whose machine is already chosen.

  4. N = 5. You want reads and writes to each survive two replicas being down, and reads to always overlap the latest write. Pick W and R.
    Answer

    W = 3, R = 3. Each needs 3 of 5, so each survives 5 - 3 = 2 failures, and 3 + 3 = 6 > 5 guarantees overlap.

  5. Two clients update the same shopping cart on opposite sides of a partition. What does a vector-clock store do, and what does an LWW store do?
    Answer

    The vector-clock store sees the versions are concurrent and keeps both as siblings, so the app can merge the carts. LWW keeps the one with the later timestamp and silently drops the other.

  6. Two replicas each hold 1 million keys split into 1,024 buckets, and exactly one bucket differs. About how many hashes must they compare with a binary Merkle tree?
    Answer

    About 2 × log2(1024) = 2 × 10 = 20 hashes along one path (plus the root), instead of 1 million keys.

18. Key Terms

Hash ring
The hash output space treated as a circle, holding both keys and servers.
Virtual node (vnode / token)
One of many ring points owned by a single physical server.
Preference list
The N distinct servers that hold copies of a key.
Quorum (N, W, R)
Replication factor, write acks needed, and read replies needed.
Sloppy quorum
Counting stand-in nodes toward W or R when real replicas are down.
Hinted handoff
A stand-in node keeps a write plus a note, then hands it to the real owner later.
Vector clock
Per-node counters attached to a value that detect concurrent writes.
Siblings
Conflicting versions of one key kept side by side until merged.
Tombstone
A marker that records a delete so repair does not resurrect old data.
Anti-entropy
Background repair that finds and fixes replica differences, often with Merkle trees.
Gossip
Nodes swap membership info with random peers until everyone agrees.

19. Further Reading


⌂