Skip to content
Vinh Tran
All projects

Redis Lite

A Redis server built from scratch in C++

Period
January 2026 — February 2026
Stack
  • C++
  • Go
  • Docker
  • AWS EKS
  • Terraform
  • GitHub Actions
375,000 or more pipelined SET and GET operations per second.
Five times the pipelined write throughput after adding a dedicated IO writer thread.
Failover completes in under five seconds.

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.