Redis Lite
A Redis server built from scratch in C++
- Period
- January 2026 — February 2026
- Stack
- C++
- Go
- Docker
- AWS EKS
- Terraform
- GitHub Actions
Why build a Redis
Reading about an event loop and writing one are different kinds of understanding. I wanted the second kind, so I implemented a Redis-compatible server in C++ and pushed it until the numbers stopped improving.
The constraint I set was the one Redis itself accepts: a single thread handling all command execution. No locks around the keyspace, no per-connection threads, no shared mutable state to coordinate. Every performance gain has to come from doing less work per operation or from batching syscalls, which is exactly the discipline I wanted to practise.
The event loop
The core is a single-threaded epoll loop. Every client socket is registered once and the loop reacts to readiness rather than polling, so idle connections cost nothing beyond a file descriptor.
The gain that mattered most was client buffering. Rather than issuing a write syscall per reply, replies accumulate in a per-client output buffer and flush once per loop iteration. Under a pipelined workload — where a client sends many commands without waiting for responses — this collapses thousands of small writes into a handful of large ones. That is the bulk of the 375K ops/sec figure.
Durability without stalling the loop
Fsync is slow and it does not belong on the thread that serves commands. I moved persistence onto a dedicated IO writer thread, so the event loop hands off work and immediately returns to serving clients.
Clients that need a durability guarantee use a WAIT sync barrier, which blocks only the caller until its writes are acknowledged rather than blocking the server. Separating the two paths — fast by default, durable on request — produced a five-fold improvement in pipelined write throughput over the version that synced inline.
Replication and failover
Replication supports partial resync, so a replica that briefly disconnects catches up from an offset instead of pulling a full snapshot. Full resyncs are the expensive path, and avoiding them is what keeps a brief network blip from turning into a long rebuild.
A separate health monitor written in Go watches the cluster and promotes a replica when the primary stops responding, switching roles automatically. End to end, failover completes in under five seconds.
Proving it works
The server has over 90% test coverage across GoogleTest for the C++ core and Pytest for protocol-level integration tests. A GitHub Actions pipeline builds, tests, and deploys to Kubernetes on every push, with the cluster itself defined in Terraform and running on AWS EKS.
Coverage on a project like this is less about catching regressions than about being able to change the event loop at all. Without the protocol tests I would not have been willing to touch the buffering logic.