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.
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 -qThe 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!
- Ultra-Fast I/O Engine: Built using Linux
epollfor 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-clitool to interactively run commands against the server.
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]
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 3000Because RAMpage natively supports RESP, you can use the official redis-cli tool to interact with it:
redis-cli -p 2006127.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 -qBecause 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.
-
src/— C++ source code for the RAMpage database server and CLI.server/— Contains theepollTCP server.database/— The in-memory data structures and logic.commands/— Handlers bridging raw strings to theDatabasemethods.persistence/— ThePersistenceManagerhandling AOF logging and replay.
-
tests/— Server tests and Node.js testing playground. -
docs/— Internal design notes.
A collection of deliberate engineering decisions that make RAMpage reliable and efficient.
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.
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.
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::queueand immediately continues handling the next client — no disk I/O on the hot path. - Consumer (background flusher thread): sleeps on a
std::condition_variableuntil 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.
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
RESPParserintercepts incoming TCP byte streams (handling pipelining and partial frames) and converts them to tokens. The database executes the command, and aRESPSerializerformats 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.