Skip to content

Repository files navigation

redis-clone-cpp

CI

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

Highlights

  • Works with redis-cli and 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/WATCH transactions, 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)

Quick Start

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-cpp

Then 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-cpp

The 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.

Architecture

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.

Supported Commands

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, and UNWATCH
  • Pub/Sub with channel and pattern subscriptions
  • RDB-style snapshots and AOF persistence
  • Authentication with requirepass and 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.

Benchmarks

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.sh

The 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.

Persistence

  • RDB-style snapshots: SAVE and BGSAVE serialize 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, BGREWRITEAOF compacts the log while preserving TTLs and writes that arrive during the rewrite.
  • Durability controls: AOF supports always, everysec, and no fsync 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.

Testing

cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
ctest --test-dir build --output-on-failure

The 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.

View CI runs

Security and Correctness

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.

Limitations

  • The server is single-node and intentionally omits the features listed above.
  • Traffic, including the shared AUTH password, 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.
  • maxmemory currently 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 Config but are not yet wired to configuration directives.

About

Redis-compatible in-memory datastore in modern C++ with RESP, persistence, and portable event loops.

Topics

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages