Skip to content

Repository files navigation

RAMpage

License C++ Node.js

RAMpage is a Redis-inspired, high-performance in-memory database server implemented from scratch in C++. It rampages through reads and writes with ultra-low latency, leveraging modern Linux I/O via epoll.

By natively speaking the RESP (REdis Serialization Protocol), RAMpage is 100% compatible with the official redis-cli, redis-benchmark, and any Redis client library.


📊 Benchmark Results

Benchmarks run using redis-benchmark with 100,000 requests per command on the same machine, comparing RAMpage head-to-head against a real Redis server.

(Note: Redis and RAMpage were running with persistence disabled).

redis-benchmark -p <port> -t ping,set,get,lpush,lpop,rpush,rpop,lrange -n 100000 -q

The results are incredible: RAMpage delivers significantly lower latency across almost every single core command, often responding 2x to 4x faster than Redis on SET, GET, LPUSH, and POP. While Redis maintains a slight edge in raw throughput, RAMpage is incredibly competitive. The only major gap remains in LRANGE (large list serialization), which is a known optimization target.

Command Redis (req/s) RAMpage (req/s) Throughput Verdict Redis p50 (ms) RAMpage p50 (ms) Latency Verdict
PING_INLINE 93,633 89,047 Redis 1.05x faster 0.407 0.295 RAMpage 1.4x faster
PING_MBULK 100,806 86,207 Redis 1.2x faster 0.391 0.223 RAMpage 1.8x faster
SET 97,561 81,833 Redis 1.2x faster 0.455 0.255 RAMpage 1.8x faster
GET 99,305 82,305 Redis 1.2x faster 0.415 0.335 RAMpage 1.2x faster
LPUSH 97,371 82,305 Redis 1.2x faster 0.447 0.095 RAMpage 4.7x faster
RPUSH 98,425 54,377 Redis 1.8x faster 0.447 0.103 RAMpage 4.3x faster
LPOP 96,993 81,235 Redis 1.2x faster 0.455 0.095 RAMpage 4.8x faster
RPOP 94,429 82,305 Redis 1.1x faster 0.463 0.127 RAMpage 3.6x faster
LRANGE_100 67,659 21,906 Redis 3.1x faster 0.391 2.151 Redis 5.5x faster
LRANGE_300 38,820 8,678 Redis 4.5x faster 0.647 5.455 Redis 8.4x faster
LRANGE_500 26,062 5,564 Redis 4.7x faster 0.959 8.711 Redis 9.1x faster
LRANGE_600 21,730 4,645 Redis 4.7x faster 1.119 10.279 Redis 9.2x faster

Note

PING_INLINE and LRANGE gaps are expected: PING_INLINE uses a plain-text format that requires a fallback parsing path, and LRANGE involves serializing large lists into RESP arrays — both are areas targeted for future optimization. On the core SET/GET/POP workloads that matter most for a cache, RAMpage is incredibly competitive and often faster than Redis itself!


⚡ Core Features

The C++ Database Server

  • Ultra-Fast I/O Engine: Built using Linux epoll for non-blocking, event-driven networking. Handles thousands of concurrent persistent TCP connections on a single thread with zero race conditions.
  • Native RESP Protocol: Speaks the exact same REdis Serialization Protocol used by Redis, making it compatible with the entire Redis ecosystem out of the box.
  • String & List Operations: Full support for Redis-like primitives (SET, GET, DEL, LPUSH, RPOP, LRANGE, etc.).
  • TTL & Expiry: Native support for key expiration (EXPIRE, TTL) automatically managed by the database.
  • Pub/Sub Messaging: Fully compatible publish/subscribe engine supporting exact channels (SUBSCRIBE) and glob-style pattern matching (PSUBSCRIBE).
  • Persistence (AOF): All write commands are automatically persisted to an Append-Only File (rampage.rampage). On server restart, the log is fully replayed to restore in-memory state — no data loss.
  • Interactive CLI: Comes with a rampage-cli tool to interactively run commands against the server.

🛠️ Commands Supported

Strings:

  • SET <key> <value> [ttl_seconds]
  • GET <key>
  • DEL <key>
  • TTL <key>
  • EXPIRE <key> <ttl_seconds>
  • APPEND <key> <value>
  • STRLEN <key>

Lists:

  • LPUSH <key> <value> [ttl_seconds]
  • RPUSH <key> <value> [ttl_seconds]
  • LPOP <key>
  • RPOP <key>
  • LLEN <key>
  • LINDEX <key> <index>
  • LSET <key> <index> <value>
  • LRANGE <key> <start> <stop>

Pub/Sub:

  • SUBSCRIBE <channel> [channel ...]
  • UNSUBSCRIBE [channel [channel ...]]
  • PSUBSCRIBE <pattern> [pattern ...]
  • PUNSUBSCRIBE [pattern [pattern ...]]
  • PUBLISH <channel> <message>
  • PUBSUB <subcommand> [args]

🚀 Getting Started

1. Run the RAMpage Server with Docker

Since RAMpage utilizes the Linux native epoll library, the easiest way to get everything running on any platform is by using Docker.

# 1. Build the Docker image
docker build -t rampage-server .

# 2. Run the Docker container
# This maps port 2006 on your host to port 2006 in the container
docker run -p 2006:2006 -d --name rampage-instance rampage-server

# Or specify a custom port (e.g., 3000):
# docker run -p 3000:3000 -d --name rampage-instance rampage-server --port 3000

2. Connect via redis-cli

Because RAMpage natively supports RESP, you can use the official redis-cli tool to interact with it:

redis-cli -p 2006
127.0.0.1:2006> SET name "Alice"
OK
127.0.0.1:2006> GET name
"Alice"
127.0.0.1:2006> RPUSH tasks "Email Users"
(integer) 1

(RAMpage also ships with a lightweight built-in CLI: docker exec -it rampage-instance /app/rampage_cli)

You can also test the database's throughput using the official benchmark tool:

redis-benchmark -p 2006 -t set,get -n 100000 -q

3. Use an existing Redis SDK

Because RAMpage uses the exact same wire protocol as Redis (RESP), you can use any standard Redis client natively (like redis in Node.js, redis-py in Python, or go-redis in Go). All you have to do is point the SDK to the RAMpage port (default 2006) instead of the default Redis port (6379).

// Using the official 'redis' npm package
import { createClient } from 'redis';

const client = createClient({ url: 'redis://127.0.0.1:2006' });
await client.connect();
await client.set('name', 'Alice');

Note

Command Support Caveat: While RAMpage is protocol-compatible with Redis, it does not yet support every single Redis command. Currently, it supports core String and List operations. If you send an unsupported command, RAMpage will safely return an ERR: unknown command response.


🏗️ Project Architecture

  • src/ — C++ source code for the RAMpage database server and CLI.

    • server/ — Contains the epoll TCP server.
    • database/ — The in-memory data structures and logic.
    • commands/ — Handlers bridging raw strings to the Database methods.
    • persistence/ — The PersistenceManager handling AOF logging and replay.
  • tests/ — Server tests and Node.js testing playground.

  • docs/ — Internal design notes.


🔬 Under the Hood

A collection of deliberate engineering decisions that make RAMpage reliable and efficient.

Single-Threaded Epoll — Zero Lock Contention on the Database

Instead of spawning one OS thread per client (which would require mutexes around every database read/write), RAMpage uses a single main thread with Linux epoll. The event loop wakes up only when a client socket has data ready, processes it fully, and goes back to waiting. Because only one thread ever touches the database, there are zero race conditions on data — no mutexes, no deadlocks, no lock contention. This is the same architectural choice Redis makes.

Append-Only File Persistence — Surviving Crashes and Restarts

Every successful write command (SET, DEL, LPUSH, EXPIRE, etc.) is logged to rampage.rampage before the response is sent. On restart, the server replays the entire log to restore state before accepting any client commands.

TTL correctness across restarts: When a key is set with a TTL (e.g. SET foo bar 60), naively replaying SET foo bar 60 on restart would reset the TTL back to 60 seconds — the key would live longer than originally intended. To fix this, the persistence layer rewrites TTL commands to store the absolute expiry epoch instead of a relative duration. SET foo bar 60 is logged as SET foo bar + EXPIRYAT foo <unix_epoch_ms>. On replay, EXPIRYAT sets the exact same deadline regardless of how much time has passed since the original command ran.

Non-Blocking AOF Writes — Producer/Consumer with Lock-Minimized Swap

Writing to disk on every command in the main thread would stall client responses. RAMpage solves this with a background flusher thread:

  • Producer (main epoll thread): after a successful write command, pushes the log entry into an in-memory std::queue and immediately continues handling the next client — no disk I/O on the hot path.
  • Consumer (background flusher thread): sleeps on a std::condition_variable until entries appear, then atomically swaps the entire shared queue into a local queue (holding the mutex for ~1 nanosecond), releases the lock, and writes to disk at its own pace.

The key insight is the swap trick: the mutex is held only for a pointer swap, not for any file I/O. The main thread is almost never blocked. This eliminates the producer/consumer race condition (std::queue is not thread-safe) while keeping disk writes completely off the hot path.

The RESP Network Protocol — 100% Redis Ecosystem Compatibility

Instead of inventing a custom binary format, RAMpage natively implements the REdis Serialization Protocol (RESP).

  • The Protocol: All data sent over TCP is formatted into strict RESP types: Simple Strings (+OK\r\n), Errors (-ERR\r\n), Integers (:1\r\n), Bulk Strings ($5\r\nhello\r\n), and Arrays (*2\r\n...).
  • The Architecture: RAMpage achieves this by cleanly separating the networking layer from the core database. A stateless RESPParser intercepts incoming TCP byte streams (handling pipelining and partial frames) and converts them to tokens. The database executes the command, and a RESPSerializer formats the internal result back into standard RESP bytes.
  • Why it matters: By perfectly mimicking Redis on the wire, RAMpage is instantly compatible with thousands of existing open-source tools. You don't need special drivers — any Node.js, Python, or Go Redis client can connect to RAMpage natively. It also allows us to stress-test the server using industry-standard tools like redis-benchmark.

About

High-performance Redis-inspired in-memory database server built in C++ with epoll, TCP sockets, AOF persistence and TTL support.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Contributors

Languages