A C++20 in-memory server that implements a documented subset of Redis commands over RESP2.
C++20 · CMake · RESP2 · epoll · kqueue · Docker
Quick Start · Architecture · Supported Commands · Benchmarks · Security
- Works with
redis-cliand RESP2 clients for the documented command subset - Single-threaded, nonblocking event loop with epoll on Linux and kqueue on macOS/BSD
- 114 registered commands across strings, lists, hashes, sets, and sorted sets
MULTI/EXEC/WATCHtransactions, Pub/Sub, and key expiry- RDB-style snapshots and append-only persistence with background rewrites
- Cross-platform CI plus Docker integration testing over the published TCP port
- Measured 68.9K GET/s without pipelining and 1.00M GET/s at pipeline depth 16 (50 clients, 100K requests, median of five runs on an Apple M3)
Build and run the container:
git clone https://github.com/ethanschweiger/redis-clone-cpp.git
cd redis-clone-cpp
docker build -t redis-clone-cpp .
docker run --rm -p 127.0.0.1:6379:6379 -v rediscpp-data:/data redis-clone-cppThen use a real Redis client from another terminal:
$ redis-cli SET hello world
OK
$ redis-cli GET hello
"world"The image runs as a non-root user and stores snapshot/AOF data in /data.
If you replace the image's default arguments, include --bind 0.0.0.0 so
the published port remains reachable. See the
runtime reference for an example.
To build and run directly on the host:
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
./build/redis-server-cppThe server defaults to 127.0.0.1:6379 with RDB-style snapshot persistence.
It also accepts redis.conf-style files and command-line overrides; see
Configuration.
One thread owns the keyspace and executes commands atomically, avoiding locks in the command path. Client sockets are nonblocking and multiplexed through a small platform abstraction backed by epoll on Linux and kqueue on macOS/BSD. RESP parsing, command dispatch, storage, expiry, Pub/Sub, and persistence are separate components.
Sorted sets combine a hash map for average constant-time score lookup with a skip list
for ordered rank/range operations. Background snapshots and AOF rewrites use
fork() and copy-on-write so the event loop can continue serving clients.
Read the architecture walkthrough for the component map and request/persistence paths.
The v1.0.0 feature scope is frozen. Further work is focused on correctness, security, documentation, and measurement.
Implemented
- RESP2 multi-bulk requests and inline commands
- Strings, lists, hashes, sets, and sorted sets
- Key expiry with lazy removal and active sweeps
MULTI,EXEC,DISCARD,WATCH, andUNWATCH- Pub/Sub with channel and pattern subscriptions
- RDB-style snapshots and AOF persistence
- Authentication with
requirepassand bounded client buffers - epoll and kqueue event backends
Intentionally out of scope
- Replication and Redis Cluster
- Lua scripting, streams, HyperLogLog, and geo commands
- RESP3, TLS, multi-user ACLs, and IPv6
See the complete command matrix.
The repository includes a repeatable redis-benchmark harness for standalone
GET/SET and pipelined GET/SET workloads. On an Apple M3, with 50 clients and
100,000 requests per run, the median of five runs measured:
| Workload | Requests/sec | p50 | p95 | p99 |
|---|---|---|---|---|
| GET | 68,871 | 0.391 ms | 0.447 ms | 0.671 ms |
| SET | 70,972 | 0.383 ms | 0.431 ms | 0.655 ms |
| GET, pipeline 16 | 1,000,000 | 0.391 ms | 1.087 ms | 1.119 ms |
| SET, pipeline 16 | 434,783 | 1.807 ms | 1.895 ms | 3.631 ms |
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
bash benchmarks/run_benchmarks.shThe runner launches a fresh local server, performs a warm-up, repeats every workload, and records raw output plus machine/build metadata. See BENCHMARKS.md for the complete environment, per-run throughput, methodology, and interpretation.
- RDB-style snapshots:
SAVEandBGSAVEserialize the full keyspace to a project-specific binary format. It is conceptually similar to Redis RDB but is not byte-compatible with it. - Append-only file: successful writes are RESP-encoded and replayed at
startup. Conditional string writes record their accepted values, expiry
commands record absolute deadlines, and expiration/eviction records
DEL. Replay defers expiration until the historical command stream has been applied. On successful completion,BGREWRITEAOFcompacts the log while preserving TTLs and writes that arrive during the rewrite. - Durability controls: AOF supports
always,everysec, andnofsync policies. Snapshot saves attempt best-effort fsyncs around the final rename; the remaining I/O-failure and rewrite-window risks are documented below.
Older logs containing relative TTLs do not encode their original deadlines. A rewrite preserves the currently loaded state; it cannot reconstruct an original deadline or repair data already changed by an older replay.
More detail is in the architecture and security notes.
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
ctest --test-dir build --output-on-failureThe suite covers RESP parsing, skip-list behavior, glob/numeric utilities,
expiry, transaction versioning, configuration parsing, live socket flows,
Pub/Sub, authentication and connection limits, and persistence across process
restarts. Recovery regressions also cover aborted transaction queues,
TTL-preserving numeric updates, SET type replacement, offline expiration,
conditional writes, rewrite buffering, and multiple databases. GitHub Actions
runs the suite on both Linux and macOS. A separate CI job
builds the real Docker image and verifies PING, SET, GET, and DEL
through its published port.
Two focused reviews fixed stack exhaustion in wildcard matching and uncaught numeric-configuration exceptions. Live-server regression tests cover both crash cases; command dispatch also has an exception boundary. Shared-password authentication and per-client input/output limits bound the exposed surface.
This is a learning project, not a production Redis replacement. It has not had a comprehensive security audit and should not be exposed to an untrusted network. Read Security and correctness for the review scope, fixes, and remaining risks.
- The server is single-node and intentionally omits the features listed above.
- Traffic, including the shared
AUTHpassword, is plaintext because TLS is not implemented. - Expensive commands can occupy the single event-loop thread and delay other clients; there is no rate limiting.
- The RDB-style file is project-specific, not compatible with Redis RDB files.
maxmemorycurrently uses key count as an eviction proxy, not measured bytes or process RSS.- A crash during an AOF rewrite can lose writes buffered only in memory before rewrite finalization; see the detailed security notes.
- Background-save cadence and TCP backlog have defaults in
Configbut are not yet wired to configuration directives.