A small Redis-like in-memory key-value store built with C++17 and Linux/POSIX sockets. It uses a simple line-oriented text protocol, handles concurrent TCP clients with one thread per connection, supports TTL expiration, and rebuilds state from an append-only log on startup.
- TCP server with configurable port
- Concurrent clients using one thread per client
- Redis-style text commands:
SET,GET,DEL,EXPIRE,TTL,PING,QUIT - Thread-safe in-memory hash table storage
- Lazy TTL expiration for keys
- Append-only persistence for write commands
- Startup replay from the append-only log
- Small multi-client benchmark client
Requires Linux, make, and a C++17 compiler.
makeThis creates:
mini_redis- the serverkv_bench- the benchmark client
./mini_redis 6379 mini_redis.aofArguments:
6379is the TCP portmini_redis.aofis the append-only persistence log
If no arguments are provided, the server uses port 6379 and mini_redis.aof.
Clients send one command per line. The server returns one text response per command.
Supported commands:
SET key value
GET key
DEL key
EXPIRE key seconds
TTL key
PING
QUIT
Response behavior:
SETreturnsOKGETreturns the value ornilDELreturns1if a key was removed, otherwise0EXPIREreturns1if a TTL was set, otherwise0TTLreturns remaining seconds,-1for no TTL, or-2for missing/expired keysPINGreturnsPONGQUITreturnsOKand closes the connection- malformed commands return
ERR ...
Start the server:
./mini_redis 6379 mini_redis.aofIn another terminal:
nc localhost 6379Example session:
OK mini-redis ready
SET name ABC
OK
GET name
ABC
DEL name
1
GET name
nil
SET temp hello
OK
EXPIRE temp 30
1
TTL temp
27
PING
PONG
QUIT
OK
./mini_redis 6379 mini_redis.aofThen from nc:
SET project redis-lite
OK
QUIT
OK
Stop and restart the server with the same log file:
./mini_redis 6379 mini_redis.aofCheck the value:
GET project
redis-lite
The append-only log stores write commands such as SET, DEL, and EXPIRE. On startup, the server replays the log before accepting clients.
Start the server first:
./mini_redis 6379 mini_redis.aofRun the benchmark:
./kv_bench 6379 8 1000Arguments:
6379- server port8- number of concurrent clients1000- SET/GET operation pairs per client
Example output:
clients=8 operations_per_client=1000 total_commands=16000 elapsed_seconds=0.72 throughput_cmds_per_sec=22222 failures=0
flowchart LR
client1["Client: nc / app"] --> listener["TCP listener<br/>src/server.cpp"]
client2["Benchmark clients<br/>tools/benchmark.cpp"] --> listener
listener --> accept["accept() loop"]
accept --> thread["Client handler thread<br/>one per connection"]
thread --> parser["Command parser<br/>src/command_parser.cpp"]
parser --> router["Command execution<br/>SET / GET / DEL / EXPIRE / TTL / PING / QUIT"]
router --> store["Thread-safe KV store<br/>src/kv_store.cpp<br/>unordered_map + mutex"]
store --> ttl["Lazy TTL expiration<br/>expires_at per key"]
router --> writes["Write command guard<br/>server-level mutex"]
writes --> aof["Append-only log<br/>mini_redis.aof"]
writes --> store
startup["Startup<br/>src/main.cpp"] --> replay["Replay append-only log<br/>src/persistence.cpp"]
replay --> store
src/main.cppparses startup arguments, replays persistence, and starts the server.src/server.cppowns the TCP listener, accepts clients, spawns client threads, reads line-based commands, and writes responses.src/command_parser.cppconverts text input into typed commands and validates arity.src/kv_store.cppstores keys in anstd::unordered_mapprotected by a mutex and lazily removes expired keys.src/persistence.cppappends write commands to disk and replays them on startup.tools/benchmark.cppopens multiple client connections and measures SET/GET throughput.
Writes are protected by a server-level mutex so append-only log order matches in-memory mutation order. The key-value store has its own mutex to protect shared data for both reads and writes.