Skip to the chapter
←System DesignGGroundworkSDSystem Design
System Design/Advanced/A13
33 / 36 written chapters
/

The cover
  1. B1The mental model
  2. B2Client-server basics
  3. B3Vertical vs horizontal
  4. B4Databases in design
  5. B5Caching fundamentals
  6. B6APIs & communication
  7. B7Walkthrough: URL shortener
  1. I1Load balancing in depth
  2. I2CDNs
  3. I3Message queues & async
  4. I4Consistency models
  5. I5Replication
  6. I6Rate limiting & throttling
  7. I7Designing for availability
  8. I8Storage systems
  9. I9Search systems
  10. I10Microservices
  11. I11Security basics
  12. I12Walkthrough: feed/chat
  13. I13Walkthrough: notifications
  1. A1CAP theorem in depth
  2. A2Transactions and isolation
  3. A3Storage engines
  4. A4Sharding at scale
  5. A5Time, ordering and IDs
  6. A6Distributed consensus
  7. A7Batch & stream processing
  8. A8Fault tolerance
  9. A9Observability at scale
  10. A10Release safety
  11. A11Capacity estimation
  12. A12Case studies
  13. A13Walkthrough: key-value store
  14. A14Walkthrough: web crawler
  15. A15Walkthrough: payments
  16. A16Tradeoff thinking

SDA13 · Part 3 · Advanced

Walkthrough: a key-value store

A Dynamo-style store is not one clever idea but eight ordinary ones bolted together, and the interview is about the decision you make at each seam.

25 min read2 diagrams10 sections0 exercises
33 of 36

In this chapter

  • What you are building, and what you refuse to build
  • Capacity estimation
  • Partitioning: the ring and its settings
  • The request path: who coordinates, and what a quorum costs
  • Versioning: vector clocks against last write wins

and 5 more

about 25 min
10On this page
  1. What you are building, and what you refuse to build
  2. Capacity estimation
  3. Partitioning: the ring and its settings
  4. The request path: who coordinates, and what a quorum costs
  5. Versioning: vector clocks against last write wins
  6. Failure handling, in three layers
  7. Membership and failure detection without a coordinator
  8. The storage node: a commit log, a memtable and immutable files
  9. Follow-ups and failure modes to volunteer
  10. Recognizing it in an unseen problem

What you are building, and what you refuse to build

"Design a key-value store" (it is the sixth chapter of Alex Xu's first volume) sounds like a data-structure question and is really an assembly question. The hash map is the easy part. The task is to make it survive the loss of machines, outgrow one machine, and stay writable while the network is broken, using parts you have already met in this course. So this chapter does not re-teach them. It names each part, links the chapter that explains it, and spends its words on the seams: the choices where one part constrains the next.

The model is the one in Amazon's Dynamo paper (SOSP 2007), which the paper pins down tightly: state is stored as binary objects identified by a key, no operation spans multiple items, there is no relational schema, and the objects are relatively small, usually under 1 MB. That scope is the whole bargain. By refusing transactions, secondary indexes and range scans you get a system where every request touches one key, so every request can be routed by a hash, and capacity grows by adding machines.

RequirementDecisionWhere it is explained
More data than one machine holdsHash-partition the key space on a ring with virtual nodesSharding
Survive machine and zone lossN copies of each key on distinct machines, spread over failure domainsReplication
Stay writable during a partitionChoose availability over consistency (AP), with tunable quorumsCAP, consistency models
Concurrent writes will happenVersion every value: a vector clock or a timestampTime and ordering
Copies drift apartHinted handoff, read repair, Merkle-tree anti-entropyReplication, and below
No coordinator to failGossip membership and a local failure detectorBelow
Fast, durable local writesAn LSM storage engine: commit log, memtable, SSTablesStorage engines
The AP choice is the first decision, not a detail Everything after this row depends on saying out loud that writes keep being accepted when replicas cannot talk to each other, and that you will reconcile later. A strongly consistent store (a consensus group per key range, as in the consensus chapter) is a legitimate alternative; it needs a different list of components. Amazon DynamoDB, the managed service, is a different design from the Dynamo paper (see the replication chapter), so do not answer a Dynamo question by describing DynamoDB.

Capacity estimation

The assumptions are mine, chosen to be round; the structure of the estimate is what to copy.

  • Data: 5 billion keys × 1 KB average value = 5 TB of user data. With N = 3 replicas, 15 TB stored.
  • Traffic at peak: 100,000 reads a second and 50,000 writes a second.
  • Fan-out into replica work. Following Cassandra's read path, a read at R = 2 asks two replicas (one returns the data, one a digest), so 100,000 × 2 = 200,000 replica reads. (The Dynamo paper's coordinator instead asks all N replicas and waits for R answers: 300,000 reads, 450,000 operations in total, about 75 nodes at the same utilization.) A write goes to all three replicas, so 50,000 × 3 = 150,000 replica writes. Total 350,000 replica operations a second.
  • Node count. Take 10,000 operations a second per node as a conservative planning figure and plan to run at 60%, so 6,000 usable: 350,000 ÷ 6,000 = 58.3, so 60 nodes, 20 in each of three availability zones.
  • Storage per node: 15 TB ÷ 60 = 250 GB. Throughput, not capacity, set the node count, which is typical for this workload and is why I would not pay for 4 TB disks.
  • Write path per node: 150,000 ÷ 60 = 2,500 replica writes a second × 1 KB = 2.5 MB/s into the commit log. A 32 MiB commit log segment (33.6 MB) fills in about 13 seconds. At the storage chapter's assumed write amplification of 20, compaction writes about 50 MB/s per node. The compaction budget, not the 2.5 MB/s, is what you size disks for.
  • Cross-zone traffic: each write sends the value to two replicas in other zones: 50,000 × 2 × 1 KB = 100 MB/s, about 0.8 Gbps.
  • Growth: adding the 61st node moves 15 TB ÷ 61 = 246 GB to it. At an assumed 100 MB/s streaming throttle that takes 2,460 seconds, about 41 minutes. Scaling out is slow and should start well before you need it.

Partitioning: the ring and its settings

Hash each key onto a ring; each node owns tokens, and a key belongs to the first node clockwise from its hash. The sharding chapter works through why hash mod N fails and why virtual nodes are the real trick. The decisions that remain are quantitative. How many tokens? Cassandra 5.0's num_tokens default is 16 (older versions used 256 random tokens, and the docs say newer versions use a deterministic allocator that needs far fewer per node). For 60 nodes that is 960 tokens. What do tokens buy? When a node dies or joins, its ranges move in many small pieces to or from many neighbours instead of dumping all its load on one.

The Dynamo paper adds a decision worth knowing because it explains operational pain. Its first scheme gave each node random tokens, so the partition boundaries were set by the tokens. Moving data meant recomputing Merkle trees and archiving the key space was hard, because partitioning and placement were entangled. The paper's third scheme (Q/S tokens per node) divides the hash space into Q equal partitions and then places partitions on nodes. Placement can change without changing partitioning, a partition is one file that can be archived or shipped as a unit, and the paper reports it had the best load-balance efficiency for the same amount of per-node metadata, at a cost: changing the node count needs coordination. The takeaway for an interview is a single sentence: decouple the partition from the node that owns it, so rebalancing moves whole partitions and nothing is rehashed.

Replica placement is the next seam. The coordinator stores the key and copies it to the next N − 1 distinct clockwise nodes. With virtual nodes the next N positions can belong to fewer than N machines, so the paper builds the preference list by skipping positions until it has N distinct physical nodes, and spreads the list across data centers. In practice you extend this to racks and zones. If you remember one rule: the preference list is chosen for failure-domain diversity, not for ring adjacency.

A B C D E key k = 250 100 300 500 700 900 key k hashes to 250 first node clockwise: B, the coordinator preference list (N = 3): B, C, D C is down: the write cannot reach it E, the next healthy node, takes a hinted replica labelled "for C" sloppy quorum: B, D and E count (Dynamo) strict quorum: only B and D count, B keeps the hint (Cassandra) one token per node here; with virtual nodes each machine owns many positions on the same circle
The ring used by the first dry run. Positions run 0 to 999 clockwise from the top and are chosen for illustration.

The request path: who coordinates, and what a quorum costs

Any node can receive a request, and the key's coordinator, typically the first of the top N nodes in the preference list, runs it. The paper describes two ways to reach the coordinator. A generic load balancer sends the request to a random node, which forwards it if it is not in the key's preference list: the client needs no special library, at the cost of a possible extra hop. A partition-aware client library routes directly and skips the hop, at the cost of shipping Dynamo-specific code into every client. Either way, every node needs to know the ring, which is what gossip is for (below).

For a put the coordinator generates the new version's vector clock, writes it locally, sends it to the other replicas and succeeds when at least W − 1 others respond. For a get it asks the replicas and waits for R responses. If it gets several versions that are causally unrelated, it returns them all. The paper's own note on cost is the one to remember: the latency of a get or put is dictated by the slowest of the R or W replicas it must wait for, which is why R and W are usually set below N.

The table and the availability sum below assume strict quorums, where only the real replicas count. A sloppy quorum (Dynamo's) raises write availability, because a stand-in node can take the write, and weakens the guarantee that a read overlaps the latest write.

(N, W, R)R + W > N?Replicas that may be down for a write / a readChoose it for
(3, 2, 2)4 > 3, yes1 / 1The common setting. The Dynamo paper says (3, 2, 2) was the configuration used by several instances
(3, 3, 1)4 > 3, yes0 / 2Read-heavy, rarely written; a single dead replica blocks all writes
(3, 1, 3)4 > 3, yes2 / 0Write availability above all; any dead replica blocks reads
(3, 1, 1)2 > 3, no2 / 2Fastest and weakest: a read may miss the newest write

The availability cost is easy to compute and impressive to say. With strict quorums, assume each replica is up 99.9% of the time, independently (a generous assumption: zones, racks and bad deploys correlate failures). A write that needs 2 of 3 replicas succeeds with probability 3 × 0.999² × 0.001 + 0.999³ = 0.002994 + 0.997003 = 0.999997, about 1.6 minutes of unavailability a year. A write that needs all 3 succeeds with 0.999³ = 0.997003, which is 26.3 hours a year. Demanding one more replica costs a factor of about a thousand in unavailability, which is why (3, 2, 2) beats (3, 3, 1) for anything user-facing.

Versioning: vector clocks against last write wins

Two clients can update the same key through different coordinators during a failure. The store must either detect that or silently pick a winner, and this is the decision users feel most. The time and ordering chapter explains the clocks; the seam question is which one you attach to a value.

 Vector clock (Dynamo, Riak)Last write wins (Cassandra)
Concurrent writesDetected; both versions are kept and returned to the reader, who mergesNot detected; the higher timestamp silently wins
Cost per valueA list of (node, counter) pairs; the Dynamo paper truncates it at a threshold, its example 10 pairs, dropping the oldest, which it concedes can blur the ancestry checkOne timestamp per value (Cassandra keeps one per cell, so concurrent writes to different columns both survive)
Who paysThe application, which needs a merge function (the paper's example merges shopping carts, and notes that deleted items can resurface)The user, whose write may vanish when two clocks disagree
Failure modeSibling explosion: Riak's documentation describes objects rapidly collecting unreconciled siblings, with higher latency and memory pressureClock skew picks the wrong winner and nobody knows
Reality checkThe paper measured 99.94% of requests to Amazon's shopping-cart service seeing exactly one version in one 24-hour window; it attributes divergence mainly to concurrent writers, often automated clients, not failuresFine when writes are single-writer-per-key or idempotent overwrites

Riak's documentation says vector clocks were once its sole means of tracking object history, that they cannot say which value belonged to which update (so duplicate siblings appear), and that dotted version vectors, an option since Riak 2.0, fix that and limit siblings better, so it recommends them. The answer to give is therefore "causal metadata, preferably dotted, and an application merge where concurrent updates matter; timestamps where they do not."

Dry run: the paper's five versions of one key, and what a timestamp would have done

VersionWritten byVector clockRelation to the others
D1Sx[(Sx,1)]First version
D2Sx[(Sx,2)]Descends from D1, overwrites it
D3Sy, after a client read D2[(Sx,2), (Sy,1)]Descends from D2
D4Sz, after another client read D2[(Sx,2), (Sz,1)]Descends from D2. D3 has Sy = 1 where D4 has 0, and D4 has Sz = 1 where D3 has 0: concurrent, both kept
D5Sx, after a client read D3 and D4 and merged them[(Sx,3), (Sy,1), (Sz,1)]The read context is the element-wise maximum, [(Sx,2), (Sy,1), (Sz,1)]; Sx then increments its own counter. D5 descends from both

Now replay D3 and D4 with timestamps instead, where Sy's clock is 2 ms ahead of Sz's: D3 is stamped 10.002 and D4 is stamped 10.001. Last write wins keeps D3 and drops D4, and the client that wrote D4 is never told. With vector clocks the store knew the two writes had never seen each other, kept both, and let a reader merge them into D5. The cost was the merge code and the bookkeeping.

Failure handling, in three layers

Replicas will disagree, and each layer repairs on a different timescale and with different guarantees. The replication chapter has the full table; the assembly question is what you promise and when.

  • Hinted handoff (seconds to hours). A node that cannot reach a replica stores the write with a hint naming the intended recipient and delivers it when that node returns. The Dynamo paper's variant is a sloppy quorum: reads and writes use the first N healthy nodes of the preference list, so a write succeeds even when two of the three real replicas are unreachable, at the price that the nodes acknowledging it may not be the ones a later read asks. Cassandra deliberately differs: a write always goes to every replica, the consistency level only says how many replies to wait for, and a hint on the coordinator is repair, not an acknowledgement (other than at consistency level ANY). Cassandra's docs also call hints best effort that do not guarantee eventual consistency, and keep them by default for a 3-hour window.
  • Read repair (on the read path). In Cassandra the coordinator reads the data from one replica and digests from others; on a mismatch it fetches the full data, reconciles by timestamp and writes the winner back to the stale replicas involved, before answering. In the Dynamo paper the coordinator asks all N replicas for their versions, answers after R, and then, once the stragglers arrive, updates any node that returned a stale version, using the vector clocks. Either way it only repairs keys that are read, and Cassandra does nothing at consistency level ONE, because no digests are requested.
  • Anti-entropy with Merkle trees (days). Replicas compare hash trees over the key ranges they share and stream only what differs. This is the only layer that fixes data nobody reads, and it must run often enough to beat the tombstone grace period: Cassandra's default is 10 days, so repair every node at least every 7 days.

The Merkle arithmetic is worth doing once, using the paper's traversal (two replicas compare roots, then descend only into subtrees whose hashes differ). Cassandra instead has each replica build its whole tree by scanning the range and then compares the trees, so there the saving is in what gets streamed, not in the comparison. Suppose a node's range holds 250 GB and each side builds a tree with 216 = 65,536 leaves (an illustrative size). Each leaf covers 250 GB ÷ 65,536 ≈ 3.8 MB. Compare roots: if equal, the range is in sync after one hash. If not, descend: each level compares two child hashes, and one differing key makes exactly one path mismatch, so the comparison costs 1 + 16 × 2 = 33 hash exchanges instead of 65,536, and the repair streams one 3.8 MB leaf range rather than the whole 250 GB. The trade-off is the leaf size: more leaves mean less re-streamed data and more memory to build the tree. Dynamo notes the weakness that motivated its partitioning change: each node keeps a tree per key range it hosts, and when a node joins or leaves, many ranges change and their trees must be recalculated.

Membership and failure detection without a coordinator

There is no master to ask who is alive, so nodes tell each other. Cassandra's gossip runs every second: each node updates its own heartbeat, exchanges state with one random node, probabilistically tries nodes it believes are unreachable, and talks to a seed node if it has not already. State is versioned by a (generation, version) pair so newer information wins. Because each exchange spreads what a node knows to another, news reaches the cluster in a number of rounds that grows with the logarithm of the cluster size, so seconds for tens of nodes at one round a second (a planning estimate, not a measured figure). The Dynamo paper adds seeds: well-known nodes that every node eventually reconciles membership with, which prevents the cluster splitting into two logical rings that never learn of each other.

Deciding that a node is dead is a separate problem. Cassandra runs a variant of the phi accrual failure detector: instead of a binary timeout, each node turns the silence since the last heartbeat into a suspicion level, phi, and marks a peer down when phi reaches phi_convict_threshold (default 8, "most users should never need to adjust this"). Two seam decisions follow. First, the verdict is local: Cassandra's docs say UP and DOWN status is not propagated by gossip, so two nodes can disagree about a third, and a quorum system tolerates that. Second, the detector trades speed against false alarms. Convicting too fast during a garbage-collection pause triggers hints and re-routing for a node that was merely slow, and convicting too slowly sends requests to a dead node and adds timeouts to your p99.

The storage node: a commit log, a memtable and immutable files

Each node persists with an LSM engine, explained in the storage engines chapter; here is the path through Cassandra's version with its documented settings.

write path write mutation commit log append, sync memtable sorted, in RAM SSTable immutable compaction merge + rewrite read path read key memtable per SSTable: Bloom filter partition index then data file merge versions by timestamp acknowledged after the commit-log append (default: fsync every 10 s) the Bloom filter lets most SSTables be skipped without a disk read
The two paths on one node. The write path is a sequential append plus a memory insert; the cost is deferred to flushing and compaction. The read path is the mirror image: it pays to check several files, and the Bloom filter exists to avoid most of them.
  • Write path. Each write is appended to a local commit log (segments of 32 MiB by default) and inserted in the memtable, which keeps data sorted by key. A memtable flush writes a sorted, immutable SSTable with a partition index; background compaction merges SSTables, which is the write amplification the capacity estimate charged for.
  • Read path. The node consults the memtable and the SSTables that may hold the key. Each SSTable has a Bloom filter over its partition keys to skip files, a partition index that maps keys to positions in the data file, and a summary that samples every 128th index entry by default (in the BIG sstable format; Cassandra 5.0's newer BTI format uses a trie-based index instead). Versions found in different files are reconciled by timestamp.
  • The durability seam. The commit log defaults to periodic sync: writes are acknowledged immediately and the log is fsynced every 10,000 ms by default, so an unlucky machine crash can lose up to that window on that node. batch mode waits for the fsync, at the cost of latency. The quorum is what makes the default acceptable: an acknowledged write sits in at least two nodes' logs, and with replicas in different zones one machine failure cannot lose it. The Dynamo paper makes the same trade: it describes a write buffer that cut 99.9th-percentile latency by a factor of 5 at peak, and refines it so the coordinator picks one replica to do a durable write while still waiting only for W responses.

Dry run: a write during a failure, then three ways the dead replica catches up

The ring from the figure: nodes A to E at 100, 300, 500, 700 and 900. Key k hashes to 250, so its coordinator is B and its preference list is B, C, D. N = 3, W = 2, R = 2, Dynamo-style sloppy quorum.

TimeEventBCDE (hints)
t0put v1 via B. Clock [(B,1)]. B stores it, sends to C and D. Replies from C and D (W − 1 = 1 needed): successv1v1v1none
t1C crashesv1downv1none
t2put v2 (client passes the context [(B,1)]). Clock [(B,2)]. B stores it, sends to D, and to E as a hinted replica for C because C is unreachable. D replies: success (W = 2 met by B and D even under a strict quorum)v2downv2v2 for C
t3get with R = 2 asks B and D: both return v2, so one version, no repair neededv2downv2v2 for C
t4aPath A, hint delivered. C returns, gossip marks it up, E delivers v2 to C and deletes its copyv2v2v2empty
t4bPath B, hint lost. E crashed and lost the hint; C returns holding v1v2v1v2empty
t5bget asks C and D. D returns v2 with clock [(B,2)], C returns v1 with [(B,1)]. [(B,2)] descends from [(B,1)], so v2 wins and is written back: read repairv2v2v2empty
t6cPath C, nobody reads k. The range containing k differs between C and B in the Merkle comparison: 33 hash exchanges locate the 3.8 MB leaf range (assuming the 250 GB range from the capacity estimate), which is streamed to Cv2v2v2empty

Three things to say about it. The client never saw a failure: W = 2 was met by real replicas. The correctness of path B relied on the vector clock to prove v2 newer, which is exactly what timestamps cannot prove under skew. And path C is the only one that works for keys that nobody reads, which is why anti-entropy must exist and must be scheduled, even though hints and read repair usually do the work. (At t2, a Cassandra-style strict quorum keeps the hint on the coordinator B instead of E, and E is never involved.)

Follow-ups and failure modes to volunteer

  • Hot keys. Partitioning spreads keys, not requests. The Dynamo paper's design assumes there are enough popular keys to spread their load; a single hot key stays on its N replicas. Answer with a cache in front, key splitting (a counter as several keys summed on read) or client-side batching.
  • Deletes. A delete is a write of a tombstone; purge it only after every replica has it, which is the repair-within-grace-period rule above. Otherwise deleted data returns.
  • Multi-region. Put replicas of a preference list in several data centers, as the paper describes. Cassandra additionally lets you choose per request between a local and a global quorum (LOCAL_QUORUM and EACH_QUORUM); waiting globally costs cross-region latency of 60 to 230 ms (the course's round-trip figures).
  • Large values. Dynamo targets objects under 1 MB. Beyond that, store a pointer and keep the blob in object storage.
  • Overload on recovery. A node that returns after hours is flooded by hint replay and repair streaming at the same moment it is cold. Throttle both.
Say it like this → "I'll build a Dynamo-style AP store, because for a key-value workload I'd rather accept a write and reconcile than refuse it. Keys hash onto a ring with 16 virtual nodes per machine, and each key lives on three nodes in different zones. I'll run N = 3, W = 2, R = 2 with strict quorums, so reads and writes overlap and any one replica can fail without anyone noticing. Versions carry vector clocks where concurrent writes matter and timestamps where they do not. A missed write is repaired three ways: a hint when the node returns, read repair when a stale replica is read, and Merkle-tree anti-entropy on a schedule shorter than the tombstone grace period. Nodes learn membership by gossip and decide failure locally with a phi-accrual detector. Each node is an LSM engine, so writes are a log append and a memory insert. For the numbers, 5 TB of data is 15 TB replicated, but 350,000 replica operations a second need about 60 nodes, so throughput, not disk, sets the size."

Recognizing it in an unseen problem

  • Signals: "design a key-value store", "a cache that survives restarts", "a session store", "a shopping cart that is always writable", "a distributed hash table", or any prompt with simple get and put by key at large scale and a strict availability target.
  • First fork: must two readers never disagree (then a consensus-backed store, as in the consensus and replication chapters) or must the system always accept writes (then this design)?
  • What to refuse: multi-key transactions, secondary indexes and range scans. Naming that these are out of scope is part of the design.
  • The numbers an interviewer wants: replica fan-out (R reads and N writes per client operation), node count from throughput, write amplification, and the cost of one more replica in the write quorum.
  • The pitfalls to name unprompted: last write wins and clock skew, the hint window, tombstone resurrection if repair runs too rarely, hot keys, and that R + W > N guarantees overlap only for strict quorums: a sloppy quorum breaks it, and even strict quorums are not linearizable (see the consistency models chapter).
  • The follow-up you will get: "a node is down for a day, then comes back: what happens?" Walk the three layers in order, and say the hint window has expired so Merkle repair and read repair do the work.
Before you move on
  • Rebuild the capacity estimate: from 100,000 reads and 50,000 writes a second, get 350,000 replica operations and about 60 nodes, and say why storage did not set the size.
  • Explain preference-list construction with virtual nodes and why it skips positions owned by the same machine.
  • Compute the availability of W = 2 of 3 against W = 3 of 3 with 99.9% replicas, and say what the independence assumption hides.
  • Walk the D1 to D5 version example and say what a timestamp would have lost.
  • Trace the dry run through all three recovery paths and say which one handles a key nobody reads.
  • Say why gossip-based failure detection is local, and what a phi threshold trades.
  • Explain what the commit-log sync default risks, and what makes the risk acceptable.
Sources and further reading
  • DeCandia et al., Dynamo: Amazon's Highly Available Key-value Store (SOSP 2007) — scope, preference lists, vector clocks and the D1 to D5 example, sloppy quorum and hinted handoff, Merkle trees, seeds, (3, 2, 2), partitioning strategies
  • Cassandra architecture: Dynamo — tokens and virtual nodes, replication factor, consistency levels, gossip each second, phi accrual detection, local UP and DOWN status
  • Cassandra: storage engine — commit log, memtable, SSTables, compaction, Bloom filter, index summary, periodic and batch commit-log sync
  • Cassandra: hints — best-effort hinted handoff and the 3-hour default window
  • Cassandra: read repair — data and digest reads, repair by timestamp, no repair at consistency level ONE
  • Cassandra: repair — Merkle trees, incremental and full repair, repairing inside the grace period
  • Cassandra: cassandra.yaml reference — num_tokens 16, phi_convict_threshold 8, max_hint_window 3h, commit-log defaults
  • Riak: causal context — vector clocks, dotted version vectors, siblings and sibling explosion

Claims in this chapter were checked against these sources in October 2026, using the Cassandra 5.0 documentation. Figures marked "planning figure", "assumed" or "illustrative" (10,000 operations a second per node, a write amplification of 20, a Merkle tree of 65,536 leaves, 99.9% replica availability, the ring positions) are this chapter's own inputs, not measurements. The Dynamo paper was read as extracted text, so wording was matched approximately.

✓Practise

5 questions drawn from 7, scored out of 5. This is practice: it runs in your browser, saves nothing and does not change what you have marked read.

✎Design drill

Design a key-value store for session and cart data

"Our product teams keep asking for one place to put per-user session and cart data, and they want it to keep taking writes when a machine or a zone is down. Design the key-value store behind it. get(key) returns the stored value, or several if the store holds conflicting ones, plus an opaque version token. put(key, value, token) writes a value, passing back the token from the read when there is one. Walk me through the design, and tell me what happens when a node is down for a day."

In scope: single-key reads and writes, sizing the cluster, and failure handling. Out of scope: multi-key transactions, secondary indexes, range scans, multi-region deployment and authentication.

Planning assumptions (figures for this exercise, not facts about any real service):

  • 2 billion keys, average value 1 KB, no value larger than 1 MB.
  • Every key is stored on N = 3 replicas, and the cluster spans 3 availability zones with the same number of nodes in each.
  • Peak load is 60,000 reads and 30,000 writes per second.
  • Each read is sent to 2 replicas (R = 2) and each write is sent to all 3 replicas.
  • One node handles 10,000 replica operations per second, and you plan to run nodes at 60% of that.
  • Each node has 2 TB of disk, filled to at most 50%.
  • The store must keep accepting writes while one replica, or one whole zone, is unreachable.

Work it out on the whiteboard or on paper first. Nothing on this card is saved, and the checklist stays hidden until you ask for it.

Open a starter whiteboard
A13

Finished reading?

0 of 36 chapters read in this series

→Next stop

  1. A12Previous · A12Real-world case studies
  2. A13you are here
  3. Next · A14Walkthrough: a web crawlerA crawler is a loop that fetches pages and follows links, and every hard part is about not doing that too fast, too often, or forever.27 min read

01On this page

10 sections · 0%

  1. 01What you are building, and what you refuse to build
  2. 02Capacity estimation
  3. 03Partitioning: the ring and its settings
  4. 04The request path: who coordinates, and what a quorum costs
  5. 05Versioning: vector clocks against last write wins
  6. 06Failure handling, in three layers
  7. 07Membership and failure detection without a coordinator
  8. 08The storage node: a commit log, a memtable and immutable files
  9. 09Follow-ups and failure modes to volunteer
  10. 10Recognizing it in an unseen problem

[] chapters · t top