All lessonsDay 17B-Building blocks2026-10-13

Storage and Retrieval: How a Distributed Key-Value Store Works

Your profiles fit in a hash table on one server. The table fills up and you add eight servers. Then one server stops hearing from the others.

cache + diskSingle server path: memory cache plus disk. Fails requirement (1) when data outgrows memory.
s0 to s7Partition example: 8 servers s0 to s7 on one ring. Key key0 maps to s1.
N = 3Replication example: N equals 3. Key key0 has copies on s1, s2, s3.
W + R > NQuorum rule: strong consistency needs W plus R greater than N. Common pick is N equals 3, W equals 2, R equals 2.
1M bucketsRepair scale: 1 million buckets per 1 billion keys. Each bucket holds about 1000 keys.

Key points

Objectives

01Derive W, R, N values that give fast reads or fast writes or strong consistency.
02Break down a vector clock to tell ancestor from conflict.
03Argue when to pick availability over consistency during a partition.
04Trace a write through commit log to SSTable and a read through cache to disk.

Requirements

01

Problem: one machine runs out


A single server key-value store is a hash table in memory. Access is fast. Fit is limited.

Two optimizations help a little. Compress data. Keep only hot data in memory and the rest on disk. Even with both, a large data set fills one server quickly.

That breaks (1). The fix is a distributed hash table. Split data across many servers. Replicate each key on N servers. The rest of this lesson is how to place, copy, read, and repair that data.

Mental picture: think of servers as stops on a clock face. Keys are riders. Each rider walks clockwise to the first stop that will take them.

Try it: grow the data set. Toggle the two small fixes. Watch one server fill even with both on.

800 GB
server capacity
256 GB

one box, memory plus disk budget

effective need
800 GB

raw data with no help

verdict
breaks (1)

needs distributed hash table

Operable check: compression cuts need by about 30 percent and hot caching keeps 20 percent in memory in this toy. Push past 256 GB and (1) breaks, which is why the lesson moves to many servers.
02

Mental model: ring plus CAP triangle


Consistent hashing solves placement. Place servers on a hash ring. Hash each key onto the same ring. Store the key on the first server clockwise from its hash.

Example from the chapter: s0 through s7 sit on the ring. Key0 hashes between s0 and s1. It is stored on s1.

Two properties matter. Load spreads evenly. Add or remove of a server moves only nearby keys. Virtual nodes improve this. A big server gets more virtual spots on the ring.

Try it: slide the key hash around the clock. Press the key0 preset to land between s0 and s1. Toggle virtual nodes to see a big server claim extra spots.

30 deg

Key hashes at 30 deg. First clockwise server is s1. Stored on s1.

Add or remove of a server moves only nearby keys because only the arc before the changed server reassigns. Virtual nodes split a big server into several arcs so load spreads evenly.

Ring placement for s0 to s7. key0 between s0 and s1 lands on s1, exactly the chapter example. Follow this one key through replication next.

CAP theorem frames the tradeoff. Consistency means all clients see the same data. Availability means every request gets a response. Partition tolerance means the system works despite a split.

You can only pick two. In practice splits happen. So real stores pick CP or AP. CA cannot exist in a real distributed system.

Concrete split: replicas n1, n2, n3 hold the same key. Network drops n3. Writes to n1 cannot reach n3. Now choose. CP blocks writes to n1 and n2. That protects (2) but breaks (3). AP keeps accepting reads and writes. That protects (3) but breaks (2) until repair.

Try it: cut n3, then pick a side. This is a before and after contrast, not a sequence.

n1replica
in sync
n2replica
in sync
n3replica
in sync

No split. n1, n2, n3 agree. Pick isolate n3 to force the CAP choice.

Same key on n1, n2, n3. CP protects (2) and breaks (3). AP protects (3) and breaks (2) until repair. Bank balance display favors CP. Cart or session favors AP.
03

Mechanism: replicate and tune with quorums


Replication means N copies on N distinct servers. Logic: hash the key. Walk clockwise. Pick the first N unique servers. Unique matters when virtual nodes put two spots from one physical server in a row.

Example: N equals 3. Key0 lands before s1. Copies go to s1, s2, s3. For reliability, put replicas in distinct data centers. Connect centers with high speed networks. This protects (5).

Reads and writes go through a coordinator. The coordinator is a node acting as proxy for the client. Define three numbers. N is replica count. W is write quorum. The write succeeds after W acknowledgements. R is read quorum. The read succeeds after R responses.

W equals 1 does not mean one copy. Copies still go to N nodes. It means the coordinator waits for one acknowledgement. That is fast. It risks stale data.

Tuning:

Latency versus consistency is the price. W or R greater than 1 waits for the slowest replica in the set. W or R equals 1 returns after any replica answers.

Consistency models in the chapter:

Try it: pick a preset, then click W and R dots to resize quorums. Overlap in the middle is what gives strong consistency.

N W R
WRoverlap 1N=3 replicas

key0 on s1, s2, s3 with N equals 3. W equals 1 waits for any one ack and risks stale reads. W or R above 1 waits for the slowest in the set. W plus R above N forces overlap on a node with the latest write.
04

Mechanism: live conflict with vector clocks


Replication plus eventual consistency creates versions. Example: n1 and n2 both hold name john. Server 1 writes johnSanFrancisco as v1. Server 2 writes johnNewYork as v2 at the same time. Neither saw the other. There is no obvious winner.

Versioning treats each write as a new immutable version. A vector clock is a list of server and counter pairs per data item. Write D as D with entries like Sx comma 1.

Rules: if Si already has an entry, increment it. If not, add Si comma 1.

Walk from the chapter:

Detection rule: X is an ancestor of Y if every counter in X is less than or equal to the matching counter in Y. Example: D with s0 comma 1, s1 comma 1 is ancestor of D with s0 comma 1, s1 comma 2. No conflict.

X conflicts with Y if some counter in Y is less than the matching counter in X. Example: D with s0 comma 1, s1 comma 2 conflicts with D with s0 comma 2, s1 comma 1.

Weakness stated honestly: clients must run reconciliation. Clocks can grow. Fix is a length cap that drops oldest pairs. That can blur ancestry. Dynamo in production had not hit this limit per the chapter. Price is client complexity for high availability.

Try it: click a version to see its clock. Then test any two clocks for ancestor or conflict. Follow D1 to D5 end to end.

Select a version. D2 descends from D1. D3 and D4 are siblings from D2.

versionclockstory

Ancestor or conflict checker

X Y

Pick X and Y, then compare. Ancestor needs every counter in X at or below Y. Otherwise siblings conflict.

johnSanFrancisco v1 versus johnNewYork v2 is the same shape as D3 versus D4. Siblings stay until a client reconciles them into D5 with Sx comma 3, Sy comma 1, Sz comma 1.
05

Mechanism: failure detection and three repairs


Failure is normal at scale. One report is not enough to mark a node down. All to all multicast works but wastes messages.

Gossip protocol is decentralized detection. Each node keeps a membership list with member ID and heartbeat counter. Each node increments its own counter. Each node sends heartbeats to random nodes. Those nodes spread them further. If a counter stops rising past a timeout, the member is marked offline and the news spreads.

Example: s0 sees member 2 heartbeat stuck at 8066 while others are past 10220. s0 shares that info. Others confirm. s2 is marked down.

Watch it live: counters tick by gossip. Kill s2 and watch s0 notice the stuck heartbeat, then recover it.

memberheartbeatstatus as seen by s0

All members ticking. One stuck report is not enough to mark a node down.

Decentralized detection with bounded messages. All to all multicast wastes messages as node count grows. Gossip spreads through random peers instead.

Repair depends on duration.

Temporary failure: strict quorum would block. Sloppy quorum relaxes placement. Use first W healthy servers for writes and first R healthy for reads on the ring. Skip offline nodes. Hinted handoff stores the write elsewhere and pushes it back later. Example: s2 is down. s3 takes writes for s2. When s2 returns, s3 hands data back. This protects (3) and later restores (2).

Try it: take s2 down, send a write for key0, then bring s2 back to hand off the hint.

s1owner
s2replica
s3replica
s4standby

Healthy ring. Writes go to s1, s2, s3 for N equals 3.

Sloppy quorum uses the first W healthy servers and skips offline nodes. s3 holds the hint for s2, then hands data back, so (3) stays up and (2) is restored.

Permanent failure: use anti-entropy. Compare all data and update to newest. A Merkle tree makes this cheap. Each non leaf node holds the hash of its children. Compare roots first. If equal, data matches. If not, compare left child then right child. Sync only bad buckets.

Build with key space 1 to 12:

Sync cost tracks difference size, not total size. That is the win.

Try it: dirty one bucket, then compare from the root down. Only bad buckets sync.

Roots match. Data matches. Sync cost 0 buckets.

1 million buckets per 1 billion keys means each bucket holds about 1000 keys. Full compare moves full data. Root check prunes equal subtrees so transfer tracks difference.

Data center outage: power or network or disaster can kill a site. Fix is copies across data centers. Users reach other centers. This protects (5) at the cost of cross center links.

06

Mechanism: write path and read path on one node


Nodes share the same duties. No single point of failure. Client API is get with key and put with key and value.

Write path, based on Cassandra:

This breaks (2) briefly for speed, then converges. Commit log guards against memory loss.

Read path:

System view: clients hit any node. Coordinator routes by ring. Ring gives decentralization. Replication gives durability. Quorums give tunable correctness. Gossip plus handoff plus Merkle gives repair. Storage engine gives speed plus persistence.

Step it: this is the one stepper in the lesson. Play the full trace for key0 from client to disk and back. Press Next to start.

commit logpersist on disk
memory cachefast save
SSTable flushsorted on disk
cache checkhit returns
Bloom filterprune SSTables
SSTable readreturn to client
  1. Client put key0. Coordinator appends to commit log on disk so memory loss cannot lose it.
  2. Coordinator saves key0 in memory cache for fast access.
  3. When cache is full or hits threshold, sorted data flushes to an SSTable, a sorted list of key value pairs.
  4. Client get key0. Check memory cache first. If hit, return at once.
  5. On miss, Bloom filter tells which SSTables might hold key0 and avoids useless disk reads.
  6. Read candidate SSTables, return result to client. Coordinator routed by ring throughout.
Write breaks (2) briefly for speed, then converges. Read pays at most one memory check plus filtered disk reads. No single point of failure because every node runs this engine.
07

Code: quorum and vector clock in Python


Show code
# Simulates N replicas, W/R quorum, and vector clock comparison.
# Run with: python3 kv_demo.py

replicas = {"s0": {}, "s1": {}, "s2": {}}
N = 3

def vector_dominates(a, b):
    # Returns True if a >= b on all entries. Ancestor check.
    keys = set(a) | set(b)
    return all(a.get(k, 0) >= b.get(k, 0) for k in keys)

def clocks_conflict(a, b):
    # Conflict if neither dominates. Sibling check.
    return not vector_dominates(a, b) and not vector_dominates(b, a)

def quorum_write(key, value, clock, W, down=set()):
    # Line that matters: only count healthy nodes, sloppy quorum style.
    healthy = [s for s in replicas if s not in down]
    if len(healthy) < W:
        return False, "blocked: not enough healthy nodes"
    for s in healthy[:W]:
        replicas[s][key] = (value, dict(clock))
    return True, f"acked by {healthy[:W]}"

def quorum_read(key, R, down=set()):
    # Line that matters: caller must compare versions, eventual consistency.
    healthy = [s for s in replicas if s not in down]
    results = [replicas[s].get(key) for s in healthy[:R]]
    return [r for r in results if r is not None]

if __name__ == "__main__":
    ok, msg = quorum_write("name", "johnSF", {"Sx": 2, "Sy": 1}, W=2)
    print("write:", ok, msg)
    print("read R=2:", quorum_read("name", R=2))
    # Illustrative clocks only to show conflict logic:
    c1 = {"s0": 1, "s1": 2}
    c2 = {"s0": 2, "s1": 1}
    print("conflict?", clocks_conflict(c1, c2))
    print("ancestor?", vector_dominates({"s0": 1, "s1": 2}, {"s0": 1, "s1": 1}))

# Expected output:
write: True acked by ['s0', 's1']
read R=2: [('johnSF', {'Sx': 2, 'Sy': 1})]
conflict? True
ancestor? True
08

Field guide


09

Interview ears


10

Judgment quiz


1Predict: N equals 3, W equals 2, R equals 2. Partition isolates one replica. Can a write still succeed. Which requirement is stressed.

Two healthy nodes still form a W equals 2 quorum, so (3) holds on the majority side while the isolated copy lags on (2).

2Compare: CP block versus AP accept during n3 isolation. What breaks in each.

Blocking keeps one view of data but refuses answers. Accepting keeps answers but lets n3 diverge until repair.

3Choose: reads are 10 times writes and must be fast. Pick R and W with N equals 3 and defend the price.

R equals 1 minimizes read latency for a read heavy mix. The price is W equals 3 writes that wait for the slowest replica and stall if any replica is down, unless sloppy quorum takes the hint.

Sources: Dynamo paper on eventual consistency, vector clocks, sloppy quorum, hinted handoff, and Merkle anti-entropy. Cassandra design on ring placement, quorum tuning, commit log, SSTable, and Bloom filter reads. BigTable as sorted storage influence.