A Redis-compatible in-memory key-value database server written in C/C++, built from scratch. It implements custom core data structures, a binary serialization protocol, and an asynchronous single-threaded event loop — without relying on the STL for its internals.
- Custom data structures — intrusive doubly linked lists, hash maps, AVL trees, and a min-heap, all implemented from scratch
- Binary protocol — custom tagged serialization (
TAG_STR,TAG_INT,TAG_ARR, etc.) over TCP rather than RESP - Async event loop — single-threaded,
poll()-based multiplexing with non-blocking I/O and read/write state machines - Supported types — string key-value pairs and sorted sets (ZSet)
- TTL / expiration — per-key millisecond TTL backed by a min-heap; expired keys are evicted on each event loop tick
- Idle connection timeouts — connections inactive for more than 5 seconds are automatically closed via an intrusive idle queue
.
├── redis_server.cpp # Server entry point
├── redis_client.cpp # Client entry point
├── src/
│ ├── net/ # server, client, protocol, timers
│ └── storage/
│ ├── core/ # hashtable, avl, heap, list
│ ├── rdb.cpp # core database logic and command dispatch
│ ├── zset.cpp # sorted set operations
│ ├── buffer.cpp # I/O buffer management
│ └── entry.cpp # key-value entry representation
└── include/ # header files mirroring src/ layout
| Command | Description |
|---|---|
set <key> <value> |
Store a string value |
get <key> |
Retrieve a string value |
del <key> |
Delete a key (any type); returns 1 if removed, 0 if not found |
keys |
Return all keys (O(N)) |
| Command | Description |
|---|---|
zadd <key> <score> <name> |
Add or update a member with a float score |
zrem <key> <name> |
Remove a member |
zscore <key> <name> |
Get the score of a member |
zquery <key> <score> <name> <offset> <limit> |
Range query from a lower bound; returns alternating name, score pairs |
| Command | Description |
|---|---|
pexpire <key> <ttl_ms> |
Set a TTL in milliseconds |
pttl <key> |
Get remaining TTL in ms; -1 if no expiry, -2 if key missing |
Requirements: GCC/G++, make
make # builds both ./rdb (server) and ./client
make clean # removes build artifactsRun the server:
./rdb --host 0.0.0.0 --port 1234Run the client:
./client --host 127.0.0.1 --port 1234Requirements: Docker, Docker Compose
docker compose up serverThe server will be available at localhost:1234.
docker compose run --rm clientStarts an interactive client session connected to the running server.
docker build -t rdb .
docker run -p 1234:1234 rdb| Flag | Default | Description |
|---|---|---|
--host |
127.0.0.1 |
Address to bind (server) or connect to (client) |
--port |
1234 |
Port to bind or connect on |
Event loop — event_loop() in server.h uses poll() to multiplex all connections on a single thread. Connection state is managed via conn_state_map, enabling O(1) lookups. Each connection progresses through want_read / want_write states handled by handle_accept, handle_read, and handle_write.
Timers — Two timer mechanisms run on each loop tick via process_timers():
- Idle timeouts: an intrusive
DList idle_queuetracks last-active time per connection; connections at the head exceedingMAX_IDLE_TIMEOUT(5s) are closed. - Key expiration: a min-heap stores absolute expiry timestamps in milliseconds; keys at the top are deleted when their deadline has passed.
MIT