Skip to lesson
‹

Courses / System Design Interview / Chapter 9

Module 2 · Building Blocks · Chapter 9 of 39

Designing a Key-Value Store

17 sections · ~32 min✓ Fact-checked

Builds on Back-of-the-Envelope Estimates, Abstractions That Hide the Network, Databases: Replication, Partitioning and Choosing a Store and Consensus, Leader Election and Coordination. Next up: Content Delivery Networks.

Part 1 · What We Are Building

Requirements

It is 8:15 pm on a Saturday, an IPL match is on, and Meera's family in Bengaluru is building one dinner order in a food delivery app. Meera adds idli from her phone; her brother adds vada from his. At that moment one of the machines holding carts loses power. Nobody notices. Both items are saved, the cart still loads, and when the machine returns it quietly catches up on what it missed. That takes a store that keeps every cart on several machines, stays writable and repairs itself.

Meera's cart, key cart:17, is this chapter's running example. A key-value store does one job: given a key, store a value or return it. On one machine this is a hash map; the hard part is billions of keys across hundreds of machines, some of them always failing. Written down, the requirements are:

What we need
Operationsput(key, value) and get(key); values are small, usually well under 1 MB
ScaleBillions of keys, hundreds of thousands of requests per second; grow by adding machines
AvailabilityAlways accept writes, even with machines down; "add to cart" is never refused
ConsistencyTunable: each use case picks how fresh its reads must be
LatencyLow and predictable: a few milliseconds for most requests
Out of scopeRange scans, queries by value, transactions across several keys

A quick estimate shows why one machine will not do. Two billion keys at about 1 KB each is 2 TB; three copies make 6 TB. Add a peak of 300,000 requests per second and a rule that no single failure may stop writes, and one machine is out of the question. As Databases: Replication, Partitioning and Choosing a Store showed, we must partition the keys across machines and replicate every partition.

text
put(key, value, context)  ->  ok
get(key)                  ->  (values, context)

context carries version information for conflicts, which is also why get can return more than one value.

Decide the priority first

Carts and sessions need to stay writable more than they need perfectly fresh reads, and most of the design follows from that. A wallet chooses the other way, as the leader-per-partition page shows.

Part 2 · Spreading and Copying Keys

Consistent hashing

The first question is which machine holds cart:17. The databases chapter showed that placing keys by hash(key) mod the number of servers moves almost every key when a server is added or removed. Consistent hashing fixes this. Take the output range of a hash function, say 0 to 2^32 - 1, and bend it into a circle. Each server is hashed to a position on this ring, and each key is hashed onto the same ring. A key belongs to the first server clockwise from it.

When a server joins, it takes over only the keys between itself and the server before it. When a server leaves, only its keys move, to the next server clockwise. With 10 servers, one change moves about a tenth of the keys on average, instead of nearly all of them.

Predict before you watch: in the scene, server B leaves the ring. Which server takes its keys, and what does that do to the load?
  • The next server clockwise, C, takes all of them, so C ends up owning about two thirds of the ring while A and D share the last third.
Animated sceneKeys on a ring, then virtual nodes0:29

Watch for which keys move. Each key belongs to the next server clockwise

Read it as steps

Watch for which keys move when servers join and leave, and how extra spots per server even out the load.

One spot per server is not enough. Arcs come out uneven by chance, and when a server leaves, its whole arc lands on one neighbour, which may then overload as well. Both problems shrink when every server holds many spots.

Virtual nodes

With virtual nodes, each physical server is placed on the ring many times, for example 100 times, by hashing names like A#1, A#2 and so on. Each server then owns many small arcs scattered around the ring instead of one large arc. The two layouts compare like this:

One spot per serverMany virtual nodes per server
Load spreadUneven, by luck of the hashClose to even, because many small arcs average out
A server leavesIts whole arc lands on one neighbourIts small arcs spread over all the remaining servers
A server joinsTakes load from one neighbourTakes a little from every server
Mixed hardwareEvery server gets the same shareA bigger server gets more virtual nodes
CostA tiny ring tableA larger ring table: 100 servers with 100 spots each is 10,000 entries, still small

Virtual nodes win on every row except the size of the ring table, and even that stays small.

How even it gets

With 10 servers and one spot each, the biggest share can easily be two or three times the fair 10%. With 100 virtual nodes each, the 1,000 small arcs average out, and each server's share typically lands within about 10% to 20% of its fair share.

Every node and every client library keeps a copy of the ring, so any of them can work out where a key lives without asking a central server. How they all learn about changes to the ring is the job of gossip, later in this chapter.

Replicas and quorums

Core deep dive

R, W and N decide how fresh reads are and how many failures writes survive.

Spreading keys is half the job; the other half is keeping copies. Each key is stored on N nodes, usually 3. The first server clockwise from the key is its coordinator, and the key is also copied to the next N − 1 servers clockwise. This list of N servers is the key's preference list. While walking the ring, skip virtual nodes of a server already on the list, so the N copies sit on N different machines, ideally in different racks or zones.

Each request then picks how many replicas must answer. A write succeeds when W replicas confirm it; a read waits for R replies and returns the newest version. As in the databases chapter, R + W > N means every read overlaps the last successful write. Three common settings show the trade:

Use case (N = 3)WRWhy
Shopping cart (cart:17)22Survives one node down for both reads and writes, and every read overlaps the last write
Session store11Fastest; an occasional stale or lost session is acceptable
Feature flags read on every request31Writes are rare and wait for all three, so a write fails while any replica is down; reads ask one replica
Shopping cart (cart:17)

W2

R2

WhySurvives one node down for both reads and writes, and every read overlaps the last write

Session store

W1

R1

WhyFastest; an occasional stale or lost session is acceptable

Feature flags read on every request

W3

R1

WhyWrites are rare and wait for all three, so a write fails while any replica is down; reads ask one replica

Latency follows from the numbers. A write with W = 2 waits for the second-fastest of three replicas, so one slow or dead replica does not slow it down. Raising W or R buys freshness and costs speed and availability; lowering them does the opposite. That trade, made per request, is tunable consistency. Even the strictest setting is not the linearizable model from Abstractions That Hide the Network, as the box below explains.

R + W > N is not a lock

It promises only that the R replicas answering a read include at least one holding the latest successful write. Two concurrent writes can both succeed and conflict. While a write is still in progress, one read may see the new value and the next read the old one. The sloppy quorums on the coming pages also break the overlap for a while. Treat it as "reads are usually fresh", not as strong consistency.

One request, end to end

Put the pieces together and follow a single read of cart:17. The client library hashes the key, finds the coordinator on its own copy of the ring, and sends the request straight there. A simpler client can send to any node, which forwards the request; that costs one extra hop.

A get with N = 3 and R = 2
  1. 1Hash the keyThe client finds the key's preference list on its copy of the ring
  2. 2Send to the coordinatorThe first healthy node on the list handles the request
  3. 3Ask all three replicasAnswer after the first two replies; the third may arrive late
  4. 4Compare versionsA newer version wins; concurrent versions are all returned
  5. 5Read repairIn the background, any replica that sent an old version is sent the newest one

A put takes the same route: the coordinator stamps a new version, writes it locally, sends it to the other replicas and replies once W of them confirm. Neither path goes through a master or a central directory, which is why the store keeps working when any single machine fails. Now size the cluster that holds every cart:

How many machines?AssumptionAnswer
For space6 TB including copies; fill each node's 2 TB disk only halfway, leaving room for compaction6 nodes
For traffic300,000 requests per second, each touching about 3 replicas, is 900,000 replica operations per second; assume 50,000 per node18 nodes
PlanTake the larger number, then add headroom for a failed node and a year of growthAbout 25 nodes

Here traffic, not storage, sets the size of the cluster. Always work out both, because either one can be the limit: a store of large documents read rarely would flip it.

Part 3 · Conflicts and Failures

When two writes race

Advanced: skip on a first pass

This part shows how a leaderless store settles conflicts and repairs itself: vector clocks, hinted handoff, Merkle trees and gossip. On a first pass, skim the titles and come back when you need the depth.

Quorums decide how many replicas take a write, but not what happens when two writes to one key overlap. Without a leader, two clients can update the same key at the same moment through different nodes. Back to the opening: Meera adds idli from her phone while her brother, whose app still shows the older cart, adds vada. Both writes succeed on different replicas, and the store now holds two versions of cart:17.

The simplest answer is last write wins: stamp every write with a time and keep the highest. The careful answer is a vector clock, a small list of counters with one entry per node that has coordinated a write to this key. Version A descends from version B if every counter in A is at least as large as in B. If neither descends from the other, the writes were concurrent, and both are kept. Follow cart:17 through the race:

StepCartVector clock
Meera's first write, coordinated by node Xdosa[X:1]
Meera adds idli, again through Xdosa, idli[X:2]
Her brother, still holding [X:1], adds vada through node Ydosa, vada[X:1, Y:1]
A read finds [X:2] and [X:1, Y:1]; neither descends from the otherboth versions returnedconflict
The app merges them and writes back through Xdosa, idli, vada[X:3, Y:1]

Vector clocks found the conflict, and the app, which knows what a cart means, resolved it by keeping every item.

Last write wins or vector clocks

Vector clocks are not the only answer, and for some data they are more machinery than the data deserves. The question is what a lost write costs. The two approaches compare like this:

Last write winsVector clocks
Concurrent writesOne is silently lostBoth kept until someone merges them
Clock troubleA clock running 50 ms fast can make an older write winNo wall clocks involved
Who resolvesThe store, automaticallyThe application, when it reads
Good forSessions, caches, "last seen" timesCarts, drafts, anything you must not lose

Choose by what a lost write costs: a lost "last seen" time is harmless, while a lost dosa is a complaint from a hungry family.

Merging has its own traps

A merge by union can bring back an item that was removed on one phone. Vector clocks also grow as more nodes coordinate writes, so stores trim old entries. Some data types, such as counters and sets built as CRDTs, merge themselves; Design Collaborative Document Editing meets them again.

Conflicts come from writes that overlap. The other source of drift is a replica that misses writes altogether, and the next page starts with short outages.

Short outages: hinted handoff

Nodes go down for a few minutes all the time: a restart, a deploy, a network blip. With a strict quorum, a write needs W of the key's N home replicas; if two of three are down, the write fails. For a cart, that is unacceptable.

A sloppy quorum lets the write land on the next healthy nodes on the ring instead. The stand-in stores the value with a hint naming the real owner. When the owner returns, the stand-in hands the write over and deletes its copy. This is called hinted handoff.

Animated sceneA write while one replica is down0:22

Watch for C's copy: cart:17 lives on A, B and C, and C has just gone down

Read it as steps

Watch for where C's copy of cart:17 waits while C is down, and how it gets home.

The store stayed writable, and the copy found its way home. Now imagine B had been down too: W = 2 would have been met by A and the stand-in D. A read that reached B and C after they came back, before the hints arrived, would miss the new value entirely. That is the price of a sloppy quorum: it trades some consistency for availability, and R + W > N stops being a guarantee while hints are outstanding.

Long outages: Merkle trees

Hints cover short outages. If a node is gone for hours, a hint is lost, or a disk is replaced, replicas drift apart: if node C was away for six hours and its hints expired, its copy of cart:17 and thousands of other keys are now stale. They need anti-entropy: a background process that compares two replicas and copies over whatever differs.

Comparing key by key would mean sending hundreds of millions of keys, or their hashes, across the network for every pair of replicas. A Merkle tree avoids that. Split the key range into buckets and hash each bucket's contents. Then hash pairs of those hashes, and pairs of those, up to a single root hash. Two replicas compare roots first; if the roots match, the replicas are identical and the check is over.

Finding the one bucket that differs, out of 8
  1. 1Compare the root hashesDifferent, so something below differs
  2. 2Compare the two halvesLeft halves match: skip 4 buckets. Right halves differ
  3. 3Compare the two quarters on the rightOne matches, one differs: 2 buckets remain
  4. 4Compare the last two bucketsBucket 7 differs. Sync only the keys inside it

Each level halves the search, so when only a few buckets differ, the work grows with the depth of the tree, not its size. With about a million buckets, one difference is found in about 20 levels of comparison, and only the keys in differing buckets cross the network.

The catch

When nodes join or leave, key ranges shift and the trees for those ranges must be rebuilt. Stores keep one tree per key range and rebuild them in the background, where the cost is spread out.

Membership with gossip

Hints and routing both assume every node knows which nodes exist and which are alive, without a central server that could itself fail. Nodes use a gossip protocol, the way news spreads in a hostel: you tell a random friend, who tells another, who tells another.

  • Each node keeps a membership list: every node it knows of, with a heartbeat counter and the local time that counter last went up.
  • Every second or so, a node raises its own counter, picks a random peer, and the two swap lists, keeping the higher counter for each node.
  • If a node's counter has not risen for a while, say 10 seconds, it is suspected. If it stays silent longer, it is marked down and requests route around it.
  • A new node contacts a few well-known seed nodes to join, so the cluster never splits into groups that do not know about each other.

Because the number of nodes that know a piece of news roughly doubles every round, it reaches a 1,000-node cluster in around 10 to 15 rounds: 10 to 15 seconds at one round a second. Each node contacts only one peer per round, so the number of messages each node sends stays flat as the cluster grows.

Unreachable is not removed

Gossip decides that a node is unreachable right now, which triggers sloppy quorums and hints. Removing a node for good, which moves its keys, is usually an explicit admin action, so a flaky network never triggers a mass data move.

Part 4 · Choices in Production

The other mainstream design: a leader per partition

Everything so far is leaderless: any replica accepts writes, and disagreements are settled later. The other common design keeps the partitions but gives each one a leader. A partition's 3 replicas form a small consensus group, as in Consensus, Leader Election and Coordination; the leader puts every write to that partition in order and confirms it once 2 of the 3 replicas have it. The two designs differ on almost every axis:

Leaderless with quorumsLeader per partition
Writes during a network splitAny W reachable replicas, or stand-ins, accept themOnly the side with a majority; the minority side cannot write
ConsistencyTunable; reads are usually fresh, not linearizableLinearizable, when reads go through a leader that confirms it still leads (a lease or a quorum check)
ConflictsConcurrent writes leave versions for the app to mergeNone: the leader orders every write
When a node diesNo pause: the other replicas keep answeringIts partitions pause writes for a few seconds while new leaders are elected
LatencyThe fastest W of N replies; a slow replica is skippedLeader plus one more replica; a distant leader adds a round trip
Hard to run because ofRepairs, hints, tombstones and merge codeElections, and spreading leaders so none runs hot

Neither design wins everywhere; what the data can tolerate decides.

Names you will hear

Cassandra and ScyllaDB: leaderless with tunable quorums, for always-on writes across data centres. etcd: one consensus group, for small, critical data such as configuration. TiKV: a consensus group per key range, with transactions on top. Redis: in memory, one primary per shard with asynchronous copies, so a failover can lose recent writes; choose it for speed, not durability.

Across data centres: local or global quorums

So far every replica sat in one data centre; large stores spread them over several, so a whole site can fail without losing data. Say 3 replicas sit in Mumbai and 3 in Delhi, so N = 6, and a round trip between the two sites is about 30 ms. A quorum must now say which replicas it counts.

Local quorumGlobal quorum
Waits for2 of the 3 replicas in the caller's own data centre4 of all 6, so at least one in the other site
Write latencyA few millisecondsAt least one 30 ms round trip; about 200 ms across continents
The link between sites breaksBoth sites keep reading and writingNeither site has 4 replicas, so quorum writes stop everywhere
The other site sees a writeAfter background copying, usually within a secondStraight away: every global read overlaps it

Most traffic uses local quorums and copies to the other site in the background. The two sites then behave like the multi-leader setup in the databases chapter: both can update the same key at once, and versions or last write wins must settle it. Keep each user's writes in their home site where you can, so conflicts stay rare.

Majorities need three sites

With replicas split evenly across two sites, a broken link leaves neither side a majority. Designs that need a global majority, such as a leader per partition spread across regions, place replicas in three sites, so any two can carry on.

Operational realities

The whiteboard design hides the work of running it. Four realities decide whether a cluster stays healthy.

RealityWhyWhat teams do
A new node takes hours to joinIt streams its share of data before serving; at about 100 MB/s, 1 TB takes about 3 hoursAdd nodes days before a known peak, such as a sale or results day, never during it
Scale out at about 70% fullA dead node's data and traffic land on the rest, and merging files needs spare diskAlert at 60%; add nodes by 70% of disk or CPU
Repairs never stopHints expire and replicas drift. A replica that misses a delete for longer than tombstones live, often about 10 days, brings the value backRepair every key range within that window; rebuild a node that was down longer instead of letting it rejoin
Compaction debtHeavy writes create files faster than background merging clears them, so reads check more files and slow downWatch pending merges and read latency; throttle bulk loads

None of this shows on a design diagram, yet it decides whether the cluster survives its first big sale. The write path under Going further shows the files that compaction merges.

Part 5 · Going further

The write path inside one node

Optional: what happens inside one node when a put arrives, for readers who want to see the storage engine.

So far each node has been a box that stores keys. Inside, most stores of this kind use a log-structured design that turns scattered random writes into fast sequential ones.

What happens to one put
  1. 1Append to the commit logA fast sequential write to disk, so after a crash the write can be replayed from the log
  2. 2Insert into the memtableA sorted structure in memory; now the write is acknowledged
  3. 3Flush to an SSTableWhen the memtable grows large, often tens to hundreds of megabytes, write it out as an immutable sorted file
  4. 4Compact in the backgroundMerge SSTables, keep the newest value per key, drop overwritten and deleted data

A read checks the memtable first, then the SSTables from newest to oldest. To avoid opening every file, each SSTable has a Bloom filter, a compact bit array that answers either "definitely not here" or "possibly here". Most files are skipped after a quick check in memory.

Deletes are writes too

Files are never edited in place, so a delete writes a marker called a tombstone. Tombstones must live long enough to reach every replica. Drop them too early, and a replica that missed the delete can bring the old value back during anti-entropy.

Part 6 · Interview kit

The 45-minute answer

For the prompt "design a key-value store", here is this chapter fitted to the 40 minutes of design time in a 45-minute round.

MinutesWhat you say and drawKey number or decision
0–5Requirements: get and put by key, small values, always writable, tunable consistency. Out of scope: scans, multi-key transactionsWritable beats perfectly fresh, for carts
5–8Estimates that decide something: keys × size × copies; peak requests × replicas touched6 TB; 900,000 replica ops/s, so traffic sets about 25 nodes
8–13API: put(key, value, context), get(key) returning values and a contextThe context carries the version
13–22Hash ring with virtual nodes; N replicas on distinct machines; any node coordinatesN = 3 across zones; no master
22–37Deep dive: quorums, then conflicts, failures or the leader-per-partition optionW = 2, R = 2 for carts
37–40Second data centre with local quorums; repair, compaction, monitoringAdd nodes at 70% full

Pick conflicts if they ask about two people editing one cart; failure handling if a node dies for an hour or a disk is replaced; the leader-per-partition design if they add a wallet or compare-and-set.

Say
"For carts, N = 3, W = 2 and R = 2: we survive one node down, and every read overlaps the last successful write."
Say
"Each server gets about 100 virtual nodes, so a dead server's load spreads over all the others instead of doubling one neighbour's."
Say
"For wallets I would use a leader per partition instead: no conflicts, at the cost of a few seconds of paused writes on failover."
Avoid
Choosing last write wins for a cart without saying that it can drop an item.
Avoid
"A master node knows where every key lives." It becomes the bottleneck and single point of failure; with a ring, every node computes placement itself.

Wrap-up

TermMeaning
Consistent hashingKeys and nodes on one ring; a key belongs to the next node clockwise
Virtual nodeOne of many ring positions held by one physical server
Preference listThe N distinct nodes that store a key
QuorumHow many replicas must confirm a write (W) or answer a read (R)
Vector clockPer-node counters that tell a newer version from a concurrent one
Hinted handoffA stand-in keeps a down node's write and hands it back later
Anti-entropyBackground repair that compares replicas using Merkle trees
TombstoneA marker that records a delete until every replica has it

Next, Content Delivery Networks keeps copies of files close to users: the same trade of freshness for speed, at the edge of the internet.

Part 7 · Check yourself

Quick check

Q1Meera adds vada to cart:17 and gets "ok". A second later her brother's app reads the cart without it. With N = 3, W = 2 and R = 2, how can that happen?

Think it through, then check.

Q2A node was down for two weeks, then rejoined. Soon users see cart items they deleted days ago. What happened, and how do you prevent it?

Think it through, then check.

Q3Twelve nodes are each 85% full. One dies for good. How full do the other eleven get once its data is copied again, and why is that dangerous?

Think it through, then check.

Q4A shopping cart and a wallet balance both need a key-value store. Leaderless quorums or a leader per partition for each, and at what cost?

Think it through, then check.

Q5From the databases chapter: product wants "all open carts containing dish 88", but the store only answers get and put by key. What are your options?

Think it through, then check.

Next chapter · 10 of 39Content Delivery Networks~31 min · 2 animated scenes