Distributed Key-Value Store#
Problem statement (interviewer prompt)
Design a distributed key-value store (Dynamo / Cassandra style). It must be always-writable, horizontally scalable, tunably consistent (R+W>N), and survive node + DC failures. Explain partitioning, replication, gossip, anti-entropy, and read repair.
flowchart LR
C([Client])
R[Coordinator / Router]
N1[(Node 1<br/>range A-H)]
N2[(Node 2<br/>range I-P)]
N3[(Node 3<br/>range Q-Z)]
C --> R
R --> N1
R --> N2
R --> N3
N1 <-. replicate .-> N2
N2 <-. replicate .-> N3
N3 <-. replicate .-> N1
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
class C client;
class R service;
class N1,N2,N3 datastore;
flowchart TB
subgraph Clients
CL([App with smart driver])
end
subgraph Coordination
GOSS[Gossip - SWIM]
RING[Consistent hash ring<br/>tokens / vnodes]
SEED[Seed nodes]
REG[(Cluster meta)]
end
subgraph Node[Storage Node x N]
direction TB
COO[Coordinator]
PART[Partitioner<br/>hash key]
REP[Replicator<br/>N replicas, R/W quorum]
MEM[Memtable<br/>sorted in-memory]
WAL[[Commit log / WAL]]
SS[(SSTable on disk<br/>LSM-tree)]
COMP[Compaction<br/>size-tiered / leveled]
BF[Bloom filter per SSTable]
SUM[Index summary]
HH[Hinted handoff]
AE[Anti-entropy<br/>Merkle tree repair]
RR[Read repair]
end
subgraph Consistency[Consistency Levels]
ONE[ONE]
QUO[QUORUM<br/>R+W>N]
ALL[ALL]
LWT[Lightweight Tx<br/>Paxos / EPaxos]
CRDT2[CRDTs - opt]
end
subgraph Ops[Ops & Tooling]
SNAP[Snapshots / Backups]
REPAIR([Scheduled repair])
BAL[Token rebalancer]
MET[Metrics: read latency,<br/>pending compactions]
end
CL --> COO
PART --> RING
COO --> PART
COO --> REP --> Node
Node --> WAL --> MEM
MEM -. flush .-> SS
SS --> BF --> SUM
SS --> COMP
COO --> HH
HH -.replay.-> Node
AE --- Node
RR --- COO
GOSS --> RING
SEED --> GOSS
Consistency --- COO
Ops --- Node
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
class CL client;
class GOSS,RING,SEED,COO,PART,REP,COMP,SUM,HH,AE,RR,ONE,QUO,ALL,LWT,CRDT2,SNAP,BAL service;
class REG,MEM,SS,BF datastore;
class WAL queue;
class REPAIR compute;
class MET obs;
Write path#
- Client → coordinator node (any peer).
- Coordinator hashes key → owners on ring, picks N replicas.
- Forwards write; each replica appends to WAL, applies to memtable.
- Acknowledged when W replicas confirm.
- Memtable flushes to SSTable; background compaction merges.
Read path#
- Client → coordinator.
- Coordinator queries R replicas in parallel.
- Compares versions (vector clock / timestamp), returns latest.
- On divergence → read repair.
LSM internals#
- Memtable = skiplist / red-black tree.
- SSTables immutable, sorted by key.
- Bloom filter avoids disk reads for missing keys.
- Compaction styles: size-tiered (Cassandra default), leveled (RocksDB / LevelDB).
Anti-entropy#
- Merkle tree per range - replicas compare hashes, repair diffs.
- Hinted handoff buffers writes destined for a down replica.
Failure handling#
- Node down: gossip propagates "DN" state in seconds.
- New node: streams ranges from peers; reads from old + new during bootstrap.
- Quorum survives
(N-1)/2failures.
Glossary & fundamentals#
Concepts referenced in this design. Each row links to its canonical page; the tag column shows whether it is a high-level (HLD) or low-level (LLD) concept.
| Tag | Concept | What it is | Page |
|---|---|---|---|
HLD |
Consistent hashing | key placement with minimal remap | consistent-hashing |
HLD |
Raft / Paxos consensus | replicated state machine via majority quorum | consensus-raft-paxos |
HLD |
Leader/follower replication | sync/semi-sync/async replication, failover | replication-leader-follower |
HLD |
LSM vs B-Tree engines | WAL, memtable, SSTables, compaction | storage-engines-lsm-btree |
HLD |
Probabilistic data structures | Bloom, HLL, Count-Min, MinHash, t-digest | probabilistic-data-structures |
HLD |
Logical clocks | Lamport, vector clocks, HLC, TrueTime | logical-clocks |
HLD |
Observability | metrics, logs, traces, SLOs | observability |
LLD |
Testing strategy | pyramid, doubles, TDD, contracts | testing-strategy |
LLD |
Immutability | immutable types, persistent collections | immutability |
Quick reference#
Goals (Dynamo paper)#
- Always writable (AP).
- Symmetric, decentralized nodes.
- Incremental scalability.
- Heterogeneous hardware support.
Data model#
- key → opaque blob (or column family).
- Sometimes secondary indexes (materialized views in Cassandra).
Quorum tuning (N, R, W)#
- N = replicas, R = read consensus, W = write consensus.
R + W > N→ linearizability-like guarantee for last-writer wins.- Common: N=3, R=W=2 → strong-ish, tolerate 1 down.
- Lower W=1 for fast writes (less durable).
Conflict resolution#
- Last-writer-wins (server clock) - easy, can lose data.
- Vector clocks (Dynamo) - keep siblings, app reconciles.
- CRDT counters / sets - automatic merge.
Capacity#
- 1 PB across 100 nodes ≈ 10 TB/node.
- 100k ops/s/node common with SSD.
- Compaction can double IO; size accordingly.
Trade-offs#
- Tunable consistency is great but operationally complex.
- Wide rows in Cassandra → range scans cheap but hotspot risk.
- TTL columns → tombstones → compaction pressure.
- LWTs (Paxos) much slower than normal write.
Refs#
- Dynamo paper (SOSP '07), Cassandra design doc, ScyllaDB blog, Riak vs Cassandra comparisons, RocksDB tuning guide.
FAQ#
How do quorum reads and writes work?#
With N replicas, a write succeeds when W replicas ack and a read returns when R replicas respond. If R + W > N, every read sees the latest committed write.
What is gossip protocol in a key-value store?#
Each node periodically exchanges membership and health state with random peers. Information converges across the cluster without a coordinator, surviving partitions.
Eventual vs strong consistency: which to choose?#
Eventual consistency maximizes availability and write throughput. Strong consistency is needed when reads must reflect the latest write, like balances or unique usernames.
What is read repair?#
On a quorum read the coordinator notices replicas with stale data and writes back the latest version, repairing divergence opportunistically without a full anti-entropy run.
How does a key-value store handle node failures?#
Replicas on the hash ring keep serving traffic. Hinted handoff stores writes for the down node, and anti-entropy with Merkle trees reconciles data once it returns.
Related Topics#
- Consistent Hashing: the partitioning algorithm used by Dynamo-style key-value stores
- Replication: Leader-Follower: replication strategies that determine read/write routing in KV stores
- CAP and PACELC: the theoretical framework for the consistency vs. availability choices in KV store design
Further reading#
Curated, high-credibility sources for going deeper on this topic.