Functional Requirements
What is a Distributed Key-Value Store?#
A distributed key-value store is the simplest possible database — you give it a key, it stores a value. You give it the key again, it gives the value back. That's it. No tables, no columns, no SQL, no joins. Just put, get, and delete.
Think of it like a giant HashMap, but spread across hundreds of machines. The "distributed" part is what makes it interesting — a single-machine HashMap is trivial. The hard problems start when you need to split that HashMap across a cluster: which node holds which key? What happens when a node dies? What if two clients write the same key at the same time on different nodes?
Real-world examples: Amazon DynamoDB, Apache Cassandra, Riak. These are the systems that power shopping carts, user profiles, session stores, IoT telemetry — anything where you know the key upfront and need fast, reliable access at massive scale.
Why not a cache? Why not a document store?#
Two boundaries are important to draw early.
This is a database, not a cache. A cache (like Redis) stores data in memory — fast, but volatile. If the node restarts, the data is gone. Our system writes to disk, replicates across multiple nodes, and is designed to never lose data. It's the source of truth, not a speed layer sitting in front of one.
This is a KV store, not a document database. A document store (like MongoDB) understands the structure inside the value — it can index fields within a JSON document, query by nested attributes, run aggregations. Our system treats the value as an opaque byte blob. It doesn't know or care what's inside. You serialize before storing, deserialize after reading. The store just holds the bytes. This simplicity is what allows it to be fast and horizontally scalable — no parsing, no indexing, no query planning on the value side.
Scope decisions made during requirements#
Key is a string, value is bytes
The key is always a string — that's how the client addresses data. The value is raw bytes. It could be a string, a number, a serialized JSON, a protobuf — the store doesn't care. It stores the bytes and returns them unchanged. This is the same model Redis uses: serialize before put, deserialize after get.
Put on existing key overwrites
If you put a key that already exists, the new value replaces the old one. No error, no conflict, no versioning from the client's perspective. This is the standard behavior across DynamoDB, Cassandra, and Redis — the same way a HashMap works. You put the same key twice, the second value wins.
TTL-based expiry is supported
Data can be configured to auto-delete after a specified time. This is critical for use cases like session storage (expire after 30 minutes), temporary tokens, or IoT data that loses relevance after a window. Without TTL, the store would grow forever and operators would need to write external cleanup jobs.
Final Functional Requirements#
1. put(key, value) — store a key-value pair
- Key is a string, value is opaque bytes
- If key already exists, overwrite the value
2. get(key) — retrieve the value for a given key
- Returns the value if found, null/error if not
3. delete(key) — remove a key-value pair
4. TTL / expiry — data auto-deletes after a configured time
5. Data is persisted to disk — this is a database, not a cache
Interview framing
"Three core operations: put, get, delete. Key is a string, value is an opaque byte blob — the store doesn't parse or index it. Put on an existing key overwrites, same as a HashMap. TTL support for auto-expiry. And importantly, this is a persistent store — data goes to disk and survives restarts. We're designing a database like DynamoDB, not a cache like Redis."