Key-Value Store
Design a Dynamo-style distributed key-value store with tunable consistency and fault tolerance.
Problem Statement
Design a distributed Key-Value Store like Amazon DynamoDB, Apache Cassandra, or the storage layer behind a large metadata platform. The system accepts keys and opaque values, stores them durably, and returns values with predictable low latency under petabyte-scale data and very high write throughput.
At interview scale, assume trillions of keys, multiple petabytes of logical data, millions of writes per second, and failures as a normal condition. The hard part is not a hash map API; it is partitioning keys across a changing fleet, replicating every mutation, providing tunable consistency, detecting failures without a central coordinator, repairing divergent replicas, and keeping the write-optimized storage engine healthy.
The default design should resemble Dynamo or Cassandra: consistent hashing with virtual nodes, replication factor N, quorum reads and writes where R + W > N when stronger consistency is needed, eventual consistency for availability, vector clocks or last-write-wins for conflict handling, Merkle-tree anti-entropy, hinted handoff, read repair, gossip membership, and an LSM-tree storage engine.
Business use case
Key-value stores power shopping carts, feature state, user preferences, session metadata, device state, event deduplication, counters, IoT telemetry, catalog metadata, and high-throughput serving layers where access is dominated by direct lookup by key.
Businesses choose this architecture when they need predictable scale-out, high availability during node and zone failures, and operational control over the consistency versus latency tradeoff. It is especially valuable when the workload is write-heavy and the product can tolerate eventual consistency for some reads.
Functional Requirements
Put, get, and delete an opaque value by table name and key.
Support configurable consistency levels such as one, quorum, and all for reads and writes.
Partition keys across a dynamic node fleet using consistent hashing and virtual nodes.
Replicate each item to N distinct nodes, preferably across racks or availability zones.
Return version metadata so clients or services can detect and resolve conflicting writes.
Support TTL expiration and tombstones without blocking foreground reads and writes.
Handle node additions, removals, and rebalancing with minimal customer-visible downtime.
Expose operational APIs for ring state, replica health, repair progress, and throttling.
Non-Functional Requirements
Latency
Single-key reads and writes should complete in single-digit milliseconds at p50 and under 50ms p99 inside a region for normal object sizes. Quorum operations cost more because the coordinator waits for multiple replicas, so the system must hedge, timeout, and avoid slow replicas dominating the tail.
Availability
The store should continue serving reads and writes when individual nodes, disks, racks, or an availability zone fail. A common target is 99.99 percent or higher per region for operations at consistency one or quorum, with clearly documented degradation for all-replica operations.
Write scalability
Writes must scale by adding nodes, not by increasing coordination on one leader. The write path should be append-oriented through a WAL and memtable, then flushed to immutable SSTables. Coordinators should be stateless so write traffic can be spread across the fleet.
Durability
A write acknowledged at the selected consistency level must survive process crashes and common disk failures. Each accepting replica appends to a WAL before acknowledging, data is stored on N replicas, and anti-entropy repairs missed or corrupted copies.
Tunable consistency
Clients should choose the consistency level that matches the use case. With replication factor N, read quorum R, and write quorum W, choosing R + W > N makes at least one replica overlap between a successful read and write in the absence of concurrent writes and failed repairs. Lower consistency improves latency and availability but allows stale reads.
Elastic rebalancing
Adding or removing nodes should move only a bounded fraction of token ranges. Virtual nodes smooth capacity differences, and streaming should be throttled so rebalancing does not starve foreground traffic.
Operational safety
The design needs observability for hot partitions, quorum failures, gossip convergence, compaction debt, hint backlog, read repair rate, tombstone pressure, and disk utilization. Operators need guardrails before small imbalances become cluster-wide incidents.
Capacity Estimation
Assumptions
Assume a regional cluster storing 5 PB of logical user data with an average logical record size of 4 KB including key, value, metadata, version, checksum, and TTL fields. Use replication factor 3 across failure domains. The workload averages 1M writes per second and 2M reads per second, with a 5x peak multiplier.
The storage engine is LSM-based, so physical capacity must include replicas, WALs, indexes, Bloom filters, tombstones, and compaction headroom. Assume 8 TB of safe usable data per storage node after reserving local disk for operating system, logs, and emergency free space.
Logical data
5 PB
User-visible data before replication and LSM overhead
Average record size
4 KB
Includes key, value, metadata, version, checksum, and TTL
Stored keys
About 1.25T records
5 PB divided by 4 KB per record
Replication factor
N = 3
Replicas placed across distinct failure domains
Replicated data
15 PB
5 PB logical times 3 replicas before storage-engine overhead
Physical capacity target
25 to 30 PB
Replicated data plus 1.7x to 2.0x LSM and compaction headroom
Average write QPS
1M writes per second
4 GB per second logical ingest at 4 KB per write
Peak write QPS
5M writes per second
5x peak multiplier
Average read QPS
2M reads per second
Direct key lookups, usually one or quorum replicas
Peak read QPS
10M reads per second
5x peak multiplier
Replicated ingest bandwidth
12 GB per second average
1M writes per second times 4 KB times 3 replicas
Storage nodes
About 4,000 to 4,500 nodes
30 PB physical divided by 8 TB safe usable per node, plus spare headroom
Calculations
- Records: 5 PB divided by 4 KB per record is about 1.25 trillion records.
- Replication: with N = 3, 5 PB logical becomes 15 PB replicated before LSM overhead.
- LSM overhead: compaction, tombstones, Bloom filters, partition indexes, and WAL reserve commonly require 1.7x to 2.0x headroom, so 15 PB becomes 25.5 to 30 PB physical capacity.
- Node count: 30 PB divided by 8 TB safe usable per node is about 3,750 nodes. Add spare capacity for repairs, drains, and skew, so plan for roughly 4,000 to 4,500 storage nodes.
- Write bandwidth: 1M writes per second times 4 KB is 4 GB per second logical. With 3 replicas, the cluster writes about 12 GB per second before protocol overhead, compaction rewrite, and repair traffic.
- Peak writes: a 5x multiplier turns 1M average writes per second into 5M peak writes per second.
- Reads: 2M average reads per second and 10M peak reads per second require coordinators, network, and replica thread pools sized independently from compaction and repair work.
- Virtual nodes: with 4,000 nodes and 256 virtual nodes per physical node, the ring has about 1,024,000 token ranges, which gives fine-grained balancing and manageable streaming units.
API Design
/v1/tables/{table}/items/{key}Writes or overwrites a value for a key. The caller chooses a consistency level and may provide expected version metadata for conditional updates or conflict-aware writes.
Request
{
"valueBase64": "AAECAwQFBgc",
"contentType": "application/octet-stream",
"ttlSeconds": 86400,
"consistency": "quorum",
"expectedVersion": {
"client-a": 7,
"client-b": 2
},
"clientTimestamp": "2026-07-26T06:59:19Z"
}
Response
{
"table": "sessions",
"key": "user_123",
"version": {
"node-17": 481,
"node-42": 910
},
"replicasAcknowledged": 2,
"status": "stored"
}
200— Value stored or replaced400— Invalid table, key, value, TTL, or consistency level409— Expected version did not match current version413— Value exceeds maximum item size429— Table or tenant write limit exceeded503— Requested consistency level unavailable
/v1/tables/{table}/items/{key}Reads a value by key. At consistency one, the coordinator can return the fastest healthy replica. At quorum, it reads enough replicas to compare versions and detect stale or conflicting copies.
Response
{
"table": "sessions",
"key": "user_123",
"valueBase64": "AAECAwQFBgc",
"version": {
"node-17": 481,
"node-42": 910
},
"readConsistency": "quorum",
"conflict": false,
"ttlExpiresAt": "2026-07-27T06:59:19Z"
}
200— Value found404— Key not found or tombstone has expired from serving view409— Conflicting siblings exist and caller requested strict conflict detection429— Read limit exceeded503— Requested consistency level unavailable
/v1/tables/{table}/items/{key}Writes a tombstone for a key. Deletes must be replicated and retained long enough for anti-entropy to prevent old values from reappearing on repaired replicas.
Request
{
"consistency": "quorum",
"expectedVersion": {
"node-17": 481,
"node-42": 910
},
"tombstoneTtlSeconds": 604800
}
Response
{
"table": "sessions",
"key": "user_123",
"status": "tombstoned",
"replicasAcknowledged": 2
}
200— Delete tombstone written404— Key did not exist and idempotent delete was not enabled409— Expected version did not match503— Requested consistency level unavailable
/v1/tables/{table}/batch-getFetches multiple independent keys. The service groups keys by token range and replica preference list, then fans out subrequests while enforcing per-request limits.
Request
{
"keys": ["user_123", "user_456", "user_789"],
"consistency": "one",
"includeVersion": true
}
Response
{
"items": [
{
"key": "user_123",
"found": true,
"valueBase64": "AAECAwQFBgc"
},
{
"key": "user_456",
"found": false
}
],
"partialFailures": []
}
200— Batch completed, possibly with per-key misses400— Too many keys or invalid request429— Batch read limit exceeded503— Too many partitions unavailable
/v1/admin/tables/{table}/ringReturns ring ownership, vnode placement, replica health, and rebalancing state for operators. This is a privileged operational endpoint, not a customer data path.
Response
{
"table": "sessions",
"replicationFactor": 3,
"vnodes": 1024000,
"unavailableRanges": 12,
"streamingRanges": 340,
"hotRanges": [
{
"tokenStart": "8800000000000000000",
"tokenEnd": "8810000000000000000",
"owner": "node-313"
}
]
}
200— Ring state returned401— Authentication required403— Caller is not an operator
Keep the public API intentionally small. The core data path is single-key get, put, and delete with explicit consistency selection. Batch APIs are convenience wrappers around independent key lookups and must not promise cross-key transactions.
Large values should be rejected or redirected to object storage with the KV record storing only metadata and a pointer. Otherwise compaction, repair, and read amplification become dominated by a few oversized records.
Database Design
This question designs the database itself, so the schema below represents internal logical records rather than an application schema. The serving row is addressed by table name and key hash, then stored on the N replicas selected by the token ring.
Each storage node persists mutations in a WAL, applies them to an in-memory memtable, and flushes immutable SSTables. Metadata tables track token ownership, gossip state, hinted handoff, repairs, and compaction progress. Secondary indexes are intentionally not part of the core design because they change the problem from a key-value store into a distributed indexing system.
| table_name | varchar(128) | Logical customer table or namespace |
| key_hash | uint64 | Token produced by hashing the key |
| item_key | bytes | Original key bytes used for exact match after token routing |
| value_blob | bytes | Opaque customer value, normally bounded to a small maximum size |
| version_clock | json or binary map | Vector clock or dotted version vector for conflict detection |
| last_write_time | timestamp | Used for last-write-wins when the table chooses that policy |
| ttl_expires_at | timestamp nullable | Null means no TTL expiration |
| tombstone | boolean | Delete marker retained until repair safety window passes |
| checksum | uint32 | Detects disk or transfer corruption |
| table_name | varchar(128) | Ring can differ by table if capacity isolation is required |
| vnode_id | uuid | Virtual node identifier |
| token_start | uint64 | Inclusive start of token range |
| token_end | uint64 | Exclusive end of token range |
| owner_node_id | varchar(128) | Physical node that owns this virtual range |
| replica_rank | int | Primary, secondary, tertiary, and so on in preference list |
| state | varchar(32) | Active, bootstrapping, leaving, draining, or down |
| node_id | varchar(128) | Stable node identity |
| region | varchar(64) | Geographic or cloud region |
| availability_zone | varchar(64) | Failure domain used for replica placement |
| rack | varchar(64) | Optional lower-level failure domain |
| gossip_generation | bigint | Monotonic generation for node restarts |
| heartbeat_version | bigint | Incremented through gossip heartbeats |
| status | varchar(32) | Up, suspect, down, joining, leaving, or decommissioned |
| owned_vnodes | json | Compact list or pointer to the node token assignments |
| entry_id | uuid | Hint, read repair, or anti-entropy task id |
| target_node_id | varchar(128) | Replica that should receive the missed mutation or repair |
| table_name | varchar(128) | Table containing the affected key range |
| token_start | uint64 | Start token for range repair, or key token for a hint |
| token_end | uint64 | End token for range repair, or same as start for a point mutation |
| mutation_blob | bytes nullable | Serialized missed write for hinted handoff entries |
| created_at | timestamp | Used for expiry, retries, and repair scheduling |
| state | varchar(32) | Pending, replaying, complete, expired, or failed |
Indexes
- The primary serving lookup is table_name plus key_hash plus item_key inside the owning token range.
- Token ring metadata is indexed by table_name plus token_start so coordinators can find the replica preference list for a key hash.
- Membership is indexed by node_id and also queried by status for operator workflows.
- Repair and hint logs are indexed by target_node_id plus created_at to replay missed mutations in order and expire old hints.
- Avoid general secondary indexes in the core KV store. If needed, build them as separate asynchronous projection tables with their own partitioning and repair model.
Relationships
A logical item belongs to exactly one token range and is stored on N replica nodes selected from the ring preference list. Each physical node owns many virtual nodes, which allows fine-grained rebalancing. Membership state feeds ring placement, ring placement feeds coordinator routing, and repair logs reconcile mutations that were missed because a replica was down or partitioned.
NoSQL alternatives
The closest production systems are Dynamo-style and Cassandra-style stores. Dynamo emphasizes consistent hashing, sloppy quorums, hinted handoff, vector clocks, and application-assisted conflict resolution. Cassandra emphasizes wide-column storage, tunable consistency, gossip, hinted handoff, read repair, and an LSM-tree engine.
If the product needs strict serializable transactions, FoundationDB, Spanner, or a consensus-backed database may be a better fit. If the product mostly stores large immutable blobs, object storage plus a smaller metadata store is often cheaper than putting blobs directly into the KV engine.
High-Level Architecture
The coordinator hashes the key, consults the consistent hash ring, sends the operation to the N replica nodes, waits for the requested read or write quorum, and lets repair paths reconcile lagging replicas in the background.
The cluster has no single leader for the full keyspace. Any healthy coordinator can accept a request, compute the token for the key, find the replica preference list, and coordinate a read or write. This keeps the API tier horizontally scalable while preserving deterministic placement.
Data nodes combine coordination and storage in many real systems, but the diagram separates the coordinator role from the replica and LSM engine for clarity. Each replica persists writes locally with a WAL and memtable before acknowledging, then flushes and compacts SSTables over time. Reads use Bloom filters, partition indexes, and sometimes multiple SSTables until compaction reduces overlap.
Gossip, hinted handoff, read repair, and Merkle-tree anti-entropy are not optional add-ons at this scale. They are the mechanisms that let the system remain available during failures while eventually converging after replicas miss writes or diverge.
Request Flow
- 1
Client selects operation and consistency
The client sends a get, put, or delete with a table, key, optional TTL, and desired consistency such as one, quorum, or all. The SDK can retry idempotent operations, but it should attach request identifiers and version metadata so duplicate writes do not create ambiguous state.
- 2
Coordinator hashes the key
The load balancer routes to a healthy coordinator. The coordinator hashes the table and key into a token, consults the ring metadata, and identifies the ordered replica preference list across availability zones or racks.
- 3
Write is sent to N replicas
For a put or delete, the coordinator sends the mutation to the N replicas. Each available replica validates limits, appends the mutation to its WAL, updates the memtable, records version metadata, and acknowledges after the durable append.
- 4
Coordinator waits for W acknowledgements
The write succeeds after W replicas acknowledge. If a replica is down and sloppy quorum is allowed, the coordinator can write a hint to another healthy node so the missed mutation can be replayed later. If W acknowledgements cannot be reached before timeout, the request returns unavailable or timeout.
- 5
Read contacts R replicas
For a get, the coordinator sends requests to enough replicas for the selected consistency level. At consistency one it may return the fastest healthy response. At quorum it compares versions or digests from multiple replicas and uses the latest non-conflicting value or returns siblings.
- 6
Conflict policy is applied
If versions are concurrent, the table policy decides whether to return multiple siblings, merge with application logic, or apply last-write-wins. Last-write-wins is simple but can lose writes when clocks skew or concurrent updates race.
- 7
Read repair fixes stale replicas
When a read discovers that one replica is stale, the coordinator can send the fresh value or tombstone back to the lagging replica asynchronously. This improves convergence for hot keys without waiting for full anti-entropy repair.
- 8
Background repair and handoff continue
Hinted handoff replays missed writes after failed nodes recover. Anti-entropy workers compare Merkle trees for token ranges and stream only differing data. Compaction eventually removes overwritten values and tombstones after the safety window.
Core Components
Client SDK
Provides a simple API while exposing consistency and retry semantics.
The SDK hashes no secrets and owns no data placement authority, but it can handle endpoint discovery, deadlines, idempotency tokens, retries, backoff, and surfacing version metadata. Good client behavior is important because aggressive retries can amplify overload.
Request Coordinator
Routes each operation to the correct replica set and enforces quorum rules.
The coordinator is stateless with respect to durable data. It reads ring metadata, fans out to replicas, tracks acknowledgements, compares versions, stores hints when allowed, and returns the result once the requested consistency level is satisfied.
Consistent Hash Ring
Maps key tokens to virtual nodes and physical replicas.
The ring assigns many virtual token ranges to each physical node. Replica placement walks the ring while respecting region, zone, rack, and capacity constraints. Ring changes are versioned so coordinators and storage nodes can converge safely during rebalancing.
Replica Manager
Owns local reads, writes, tombstones, and version metadata for assigned ranges.
Each replica accepts mutations for its token ranges, persists them locally, serves reads from memtables and SSTables, participates in repairs, and reports health. It must isolate foreground traffic from compaction, streaming, and repair work.
LSM Storage Engine
Optimizes high write throughput using append-only structures.
The engine writes to a WAL, applies changes to a memtable, flushes immutable SSTables, and compacts SSTables to reduce read amplification and reclaim overwritten data. Bloom filters and indexes avoid unnecessary disk reads.
Gossip and Failure Detector
Spreads membership state and identifies suspect nodes without a central master.
Nodes exchange heartbeat and state digests with peers. A phi accrual style failure detector can mark nodes suspect based on observed heartbeat delays, reducing false positives compared with fixed timeouts.
Hinted Handoff
Preserves writes for temporarily unavailable replicas.
When a target replica is down but the write can still meet its consistency level, another node stores a hint containing the missed mutation. The hint is replayed when the replica returns, bounded by expiry and capacity limits.
Anti-Entropy Repair
Finds and repairs divergent replicas over time.
Repair workers build Merkle trees for token ranges, compare tree roots and subtrees across replicas, and stream only mismatched rows. This is essential for cold data that may never be read and therefore never benefits from read repair.
Deep Dive
Consistent hashing and virtual nodes
Consistent hashing maps the output of a hash function onto a logical ring. A key is assigned to the first token range at or after its hash, and replicas are selected by continuing around the ring while respecting failure-domain rules. When nodes join or leave, only nearby token ranges need to move instead of reshuffling the entire dataset.
Virtual nodes improve balance. Instead of giving each physical node one large range, assign it hundreds of smaller ranges. A powerful node can own more virtual nodes, a weaker node can own fewer, and rebalancing can stream small ranges gradually. This also smooths random skew in token assignment.
The tradeoff is metadata and operational complexity. More virtual nodes mean more ring entries, more streams during repair and bootstrap, and more small compaction histories. A practical design keeps enough vnodes for balance but not so many that membership changes become noisy.
Replication factor and quorum consistency
With replication factor N, every key has N preferred replicas. A write consistency W means the coordinator waits for W acknowledgements. A read consistency R means it consults R replicas. If R + W > N, then a successful read and successful write overlap on at least one replica, which reduces stale reads when there is no unresolved concurrent write.
For N = 3, common choices are W = 2 and R = 2 for quorum, W = 1 and R = 1 for lowest latency, or W = 3 and R = 1 for write durability with fast reads. The right choice is per workload. Shopping carts may prefer availability and mergeable conflicts. Payment state should not rely on this alone and may need a transactional system.
Quorums are not magic. Network partitions, timeouts, hinted writes, clock skew, sloppy quorum, and concurrent updates can still produce conflicts or stale reads. A strong candidate explains both the usefulness and limits of R + W > N.
Eventual consistency, vector clocks, and last-write-wins
In an always-writable distributed store, two clients can update the same key through different coordinators while replicas are partitioned. If neither update causally follows the other, the system must preserve or resolve the conflict.
Vector clocks track causal history by keeping counters for writers or replica actors. If one clock dominates another, the dominated version is older and can be discarded. If neither dominates, the versions are concurrent siblings. The safest approach returns siblings to the application for a semantic merge, such as combining shopping cart items.
Last-write-wins stores a timestamp and picks the largest timestamp. It is operationally simple and works for cache-like or idempotent state, but it can silently lose a valid concurrent update. If using LWW, use server-assigned hybrid logical clocks where possible and make the data loss tradeoff explicit.
LSM-tree storage engine
The write path is optimized for sequential IO. A replica appends the mutation to the WAL, applies it to an in-memory memtable, and acknowledges after durable logging. When the memtable reaches a threshold, it is flushed to an immutable SSTable sorted by key. Reads check the memtable, then recent SSTables, aided by Bloom filters and sparse indexes.
Compaction merges SSTables, drops overwritten values, purges expired tombstones after the repair safety window, and reduces the number of files a read must check. Size-tiered compaction improves write throughput but can increase space amplification. Leveled compaction improves read latency but writes more data during compaction.
At petabyte scale, compaction is often the hidden bottleneck. The design needs backpressure, compaction debt metrics, per-tenant throttles, and enough spare disk to survive a node rebuild while compaction is behind.
Anti-entropy, Merkle trees, hinted handoff, and read repair
Hinted handoff handles short outages. If replica C is down and a write reaches replicas A and B, the coordinator can store a hint for C. When C returns, the hint is replayed so C catches up. Hints should expire because a long-dead node may be too stale and should be rebuilt from streaming repair instead.
Read repair handles hot data opportunistically. When a quorum read discovers that one replica has an older version or missing tombstone, the coordinator sends the correct version back to the stale replica asynchronously. This converges keys that are frequently read.
Merkle-tree anti-entropy handles cold data. Replicas build hash trees over token ranges. If roots differ, workers descend the tree to find mismatching subranges and stream only those rows. This avoids comparing every key over the network while still proving that replicas converge.
Hot partitions, rebalancing, and failure detection
Consistent hashing balances keys, not traffic. A single celebrity key, tenant, or time-bucketed key prefix can overload one replica set even if storage bytes are balanced. Mitigations include better key design, write sharding, adaptive key splitting, hot-key caching, per-tenant throttling, and moving hot virtual nodes to stronger hardware.
Rebalancing must be controlled. When a node joins, it receives token ranges and streams data from existing replicas. If too many nodes bootstrap or repair at once, streaming competes with customer traffic and compaction. Rate-limit streams, preserve spare capacity, and avoid ring churn during incidents.
Gossip-based failure detection is eventually consistent. Marking a node down too quickly causes unnecessary hinted handoff and replica churn; marking it down too slowly increases tail latency. The failure detector should incorporate recent heartbeat variance, network conditions, and operator override states.
Scaling
Starter: one region, small replicated cluster
Begin with a few storage nodes, replication factor 3, one table namespace, and simple quorum reads and writes. Use a basic LSM engine, WAL durability, operator-visible ring metadata, and manual repair jobs. This demonstrates correctness, but it has limited isolation and rebalancing sophistication.
Growth: dozens of nodes and high write throughput
Introduce virtual nodes, rack-aware replica placement, client-visible consistency levels, compaction tuning, Bloom filters, backpressure, and automated hinted handoff. Add dashboards for p99 latency, compaction debt, disk fullness, dropped mutations, and hot token ranges.
Petabyte scale: thousands of nodes
Use hundreds of vnodes per node, token-aware routing, repair scheduling, per-tenant quotas, streaming throttles, incremental Merkle repair, and automated node replacement. Keep at least 20 to 30 percent spare capacity so repairs and rebalances do not run the fleet at full disk or network utilization.
Multi-region active-active
Replicate between regions asynchronously for low-latency local writes, or synchronously only for tables that can afford higher latency. Use region-aware version metadata, conflict policies per table, failover runbooks, and clear customer-facing consistency guarantees.
Extreme scale and noisy tenants
Add tenant isolation through dedicated tables, partitions, or fleets. Detect hot keys automatically, split or replicate hot ranges, isolate compaction pools, and provide admission control so one tenant's write burst or repair backlog does not impact unrelated workloads.
Bottlenecks & Optimizations
Hot partition or hot key
Consistent hashing cannot fix a key that receives disproportionate traffic. Use key-salting for write-heavy counters, application-level sharding, hot-key caching for reads, adaptive virtual range movement, and tenant throttles. Also teach customers to avoid monotonically increasing or time-bucket-only keys when all writes land in the newest bucket.
Compaction debt and write amplification
Monitor pending compaction bytes, SSTable count, tombstone density, and disk free space. Tune compaction strategy per workload, throttle writes before disks fill, separate compaction IO from foreground reads, and avoid storing very large values in the LSM path.
Quorum tail latency
Quorum reads and writes wait for multiple replicas, so the slowest needed replica determines user latency. Use replica health scoring, speculative reads, hedged requests, fast failure detection, and careful timeout budgets. Do not hedge so aggressively that it doubles load during incidents.
Repair backlog
If anti-entropy cannot keep up, replicas diverge and tombstones become dangerous to purge. Run incremental repair continuously, prioritize ranges with recent failures, cap concurrent streams, and alert on repair age by token range.
Gossip storms and ring churn
Frequent membership changes can destabilize coordinators and cause unnecessary streaming. Use staged node state transitions, operator approval for large decommissions, dampened failure detection, and separate transient network blips from true node loss.
Large values in a small-object store
Large values increase read latency, compaction cost, repair bandwidth, and cache inefficiency. Enforce item size limits and store large blobs in object storage, with the KV store holding metadata, checksum, and object pointer.
Failure Handling
Single replica node fails
Gossip marks the node suspect and then down. Coordinators stop sending it foreground traffic, continue operations if the requested consistency can be met, and create hints for missed writes when policy allows. When the node returns, it replays hints and runs repair for ranges that may have diverged.
Availability zone outage
Replica placement across zones allows the remaining zones to serve lower or quorum consistency depending on N, R, and W. The system may temporarily reject all-replica operations, reduce repair traffic, and reserve capacity for foreground requests until the zone recovers.
Network partition creates concurrent writes
Both sides may accept writes if consistency rules allow. Version clocks identify concurrent siblings after healing. The table policy either returns siblings to clients, invokes a merge function, or applies last-write-wins with explicit acknowledgement that one update can be lost.
Disk corruption or SSTable loss
Checksums detect corrupted blocks. The node stops serving affected ranges, fetches clean copies from other replicas through repair, and reports data-loss risk if the number of healthy replicas falls below the durability threshold. Backups protect against correlated corruption or operator error.
Coordinator crashes mid-request
Because the coordinator is stateless, clients can retry against another coordinator. Idempotency tokens, version checks, and read-before-return policies prevent duplicate mutations from being mistaken for separate successful writes.
Bad compaction or tombstone configuration
If tombstones are purged before all replicas have seen the delete, deleted values can reappear. Keep a repair safety window, monitor maximum repair age, and block tombstone purging for ranges that have not been repaired recently.
Security
Authentication and authorization
Require signed requests from applications or tenants. Authorize table-level and operation-level access, separate data-plane credentials from operator credentials, and make admin ring APIs private.
Encryption
Use TLS for client and node-to-node traffic. Encrypt data at rest on every storage node, rotate keys through a managed key service, and protect WALs, snapshots, hints, and repair streams with the same policy as primary data.
Tenant isolation and quotas
A multi-tenant KV store needs per-tenant throughput limits, storage quotas, burst budgets, and noisy-neighbor protection. Without admission control, one tenant can create hot partitions, compaction debt, or repair pressure for the whole fleet.
Auditability
Log control-plane actions such as table creation, ring changes, node decommissioning, repair overrides, and permission changes. Data-plane audit logs should be sampled or scoped because full logging of every key read can become its own large-scale system.
Backup and deletion safety
Snapshots and incremental backups must preserve encryption and access controls. Deletes should write tombstones first, then satisfy retention and legal deletion workflows without allowing old replicas or backups to resurrect data unexpectedly.
Tradeoffs
Pros
- +Scales horizontally because keys are partitioned by hash and coordinators are stateless.
- +High availability because writes can succeed without a single global leader.
- +Tunable consistency lets each workload choose latency, availability, and freshness tradeoffs.
- +LSM storage provides excellent write throughput for append-heavy workloads.
- +Virtual nodes make node additions, removals, and heterogeneous capacity easier to manage.
Cons
- −Eventual consistency exposes stale reads and conflict resolution complexity to applications.
- −Quorum semantics are subtle and do not provide full transactions or serializability.
- −LSM compaction, tombstones, and repair can create operational surprises at scale.
- −Hot keys and poor key design can overload a replica set despite balanced storage.
- −Secondary indexes and cross-key queries are hard to support without separate systems.
Alternatives
Alternative one is a leader-based sharded database. It is easier to reason about for single-key linearizability, but leader failover and cross-region writes can reduce availability or increase latency.
Alternative two is a consensus-backed distributed SQL or transactional KV system. It provides stronger guarantees and transactions, but every write typically pays consensus latency and throughput is lower for write-heavy, globally distributed workloads.
Alternative three is object storage plus a metadata database. This is better for large blobs and cheap durability, but it does not provide low-latency fine-grained updates for trillions of small keys.
When not to use this design
Do not use a Dynamo or Cassandra-style key-value store when the core requirement is ad hoc querying, joins, strict cross-key transactions, global serializability, or large blob streaming. It is also a poor fit when the application cannot tolerate stale reads or cannot define a safe conflict resolution policy.
Follow-up Questions
Why does R + W > N matter?
It ensures that the set of replicas used by a successful read overlaps with the set used by a successful write, so at least one replica can carry the latest acknowledged version under normal assumptions. It improves freshness but does not eliminate conflicts from concurrent writes, sloppy quorum, failed repairs, or clock issues.
How do virtual nodes help during rebalancing?
Virtual nodes divide ownership into many small token ranges. When a physical node joins or leaves, the cluster moves many small ranges instead of a few huge ranges, which improves balance, allows heterogeneous node capacity, and makes streaming easier to throttle.
What happens when vector clocks grow too large?
The system can prune old entries, use dotted version vectors, cap the number of actors, or move conflict resolution to a higher layer. Pruning reduces metadata but can make some causal relationships ambiguous, so it must be paired with a clear conflict policy.
Why are tombstones retained after deletes?
Deletes must be replicated like writes. If a tombstone is removed before every replica has learned about it, an old value from a stale replica can be repaired back into the cluster. Tombstones are kept until the repair safety window has passed.
How would you handle a hot key with millions of reads per second?
Use read-through caches, request coalescing, local replica caches, and possibly replicate the value beyond its normal N replicas. If it is a write-hot key, redesign the data model with sharded counters, time buckets, or application-level aggregation.
What is the difference between hinted handoff and anti-entropy repair?
Hinted handoff records specific missed mutations while a replica is temporarily unavailable and replays them when it returns. Anti-entropy repair compares ranges across replicas, often with Merkle trees, and fixes any divergence whether or not a hint exists.
Why are secondary indexes difficult in this design?
The base store partitions by primary key. A secondary index partitions by another attribute, so every write must update another distributed data structure with its own consistency, repair, backfill, and hot-key problems. Many systems build indexes asynchronously as separate projection tables.
Company Variations
Amazon
Amazon interviewers often expect Dynamo-style thinking: consistent hashing, replication factor, sloppy quorum, hinted handoff, vector clocks, operational alarms, and DynamoDB-like partition hot-spot mitigation. Be ready to discuss why availability is prioritized and where the application handles conflicts.
Databricks
Databricks may frame the problem around high-throughput metadata, job state, feature storage, or lakehouse control-plane scale. Emphasize write amplification, compaction, multi-tenant isolation, and how repair or rebalancing avoids disrupting analytical workloads.
Snowflake
Snowflake may focus on metadata correctness, separation of compute and storage, tenant isolation, and failure recovery. Discuss when a highly available KV store is appropriate for serving metadata and when stronger transactional guarantees are required.
LinkedIn can push on large-scale serving systems, member data, activity metadata, and operational reliability. Expect questions about hot keys from celebrity accounts, backfills, online rebalancing, and observability for massive fleets.
Google interviewers may compare this with Bigtable, Spanner, or internal distributed storage systems. Be clear about why this design chooses availability and tunable consistency rather than global consensus for every write.
Interview Tips
Frame the design around the main tension: the product wants always-on writes and petabyte-scale throughput, but that means accepting eventual consistency and repair complexity. Draw the ring and replica set first, then explain a write, a read, and a failure. Use N = 3 with R = 2 and W = 2 as the concrete baseline, then vary consistency levels to show tradeoffs.
What interviewers expect
- ✓Start with the API, then immediately define partitioning by consistent hashing and virtual nodes.
- ✓Explain N, R, and W with concrete quorum examples and tradeoffs.
- ✓Describe the write path through WAL, memtable, SSTable flush, and compaction.
- ✓Cover hinted handoff, read repair, and Merkle-tree anti-entropy as separate mechanisms.
- ✓Discuss conflict resolution options and when LWW is acceptable.
- ✓Call out hot keys, rebalancing, monitoring, and failure-domain-aware placement.
Common mistakes
- !Treating the system like a single hash map and skipping partitioning, replication, and repair.
- !Claiming R + W > N gives full strong consistency without discussing concurrent writes and failure modes.
- !Ignoring tombstones, compaction, and read amplification in an LSM storage engine.
- !Using last-write-wins without acknowledging clock skew and lost updates.
- !Forgetting hot partitions and assuming hashing balances traffic as well as data size.
- !Skipping operational workflows for node join, drain, repair, and replacement.
Red flags
- ×No clear replica placement strategy across failure domains.
- ×No conflict resolution story for writes accepted during partitions.
- ×No anti-entropy mechanism for cold data that is never read.
- ×No capacity math for petabytes, replicas, compaction headroom, or node count.
- ×No backpressure plan for compaction, repair, or streaming overload.
Revision Notes
- A distributed key-value store maps each key to a token using a hash function, then uses a consistent hash ring to find the replica preference list.
- Virtual nodes split ownership into many small ranges, improving balance and making node join, leave, and heterogeneous capacity easier.
- Replication factor N stores each item on N replicas across failure domains. R and W are read and write quorum sizes.
- R + W > N gives overlapping read and write quorums, but it is not the same as serializable transactions.
- Writes append to a WAL, update a memtable, and later flush to SSTables. Compaction merges SSTables, removes old versions, and purges safe tombstones.
- Bloom filters and indexes reduce read amplification by skipping SSTables that cannot contain the key.
- Vector clocks detect causal ordering and concurrent siblings. Last-write-wins is simpler but can silently lose updates.
- Hinted handoff replays missed writes after short outages. Read repair fixes stale replicas discovered by reads. Merkle-tree anti-entropy repairs cold ranges in the background.
- Hot keys require special handling because hash partitioning balances key distribution, not request distribution.
- At 5 PB logical data, replication factor 3, and LSM headroom, plan for roughly 25 to 30 PB physical capacity and thousands of storage nodes.
Flashcards
Quiz
0/8 answered
1.In a Dynamo-style store with N = 3, which R and W combination gives overlapping quorums?
2.What is the main benefit of virtual nodes?
3.Which component makes acknowledged writes durable before an SSTable flush?
4.What does a vector clock help detect?
5.Why is Merkle-tree anti-entropy useful?
6.What is the risk of purging tombstones too early?
7.Which issue is not solved by consistent hashing alone?
8.When is last-write-wins a reasonable policy?
Cheat Sheet
Goal: store opaque values by key at petabyte scale with high write throughput, low latency, high availability, and tunable consistency.
Partitioning: hash each key to a token. Use consistent hashing so node changes move limited ranges. Use virtual nodes so physical nodes own many small ranges and rebalancing is smoother.
Replication: store each item on N replicas across failure domains. With N = 3, quorum often means R = 2 and W = 2. Use lower consistency for lower latency and higher availability when stale reads are acceptable.
Write path: coordinator finds replicas, sends mutation, replicas append to WAL, update memtable, acknowledge, then later flush SSTables. Success depends on W acknowledgements.
Read path: coordinator asks R replicas, compares versions or digests, returns latest non-conflicting value or siblings, and may trigger read repair for stale replicas.
Consistency: eventual consistency is the default availability tradeoff. Vector clocks detect concurrent writes. Last-write-wins is simple but can lose updates.
Repair: hinted handoff covers short outages. Read repair fixes hot stale keys. Merkle-tree anti-entropy compares ranges and repairs cold data.
Storage engine: LSM tree with WAL, memtable, SSTables, compaction, Bloom filters, sparse indexes, tombstones, and checksums.
Scaling: watch hot ranges, compaction debt, repair age, disk fullness, gossip convergence, p99 quorum latency, and tenant-level throttling.
Do not overpromise: this design is not a general SQL database, not a global serializable transaction system, and not ideal for ad hoc secondary-index queries.
References
- PaperDynamo: Amazon's Highly Available Key-value Store — Giuseppe DeCandia and others
- PaperCassandra: A Decentralized Structured Storage System — Avinash Lakshman and Prashant Malik
- BookDesigning Data-Intensive Applications — Martin Kleppmann
- PaperBigtable: A Distributed Storage System for Structured Data — Fay Chang and others
- DocsApache Cassandra Documentation — Apache Cassandra Project