Redis protocol server, written in Rust · v0.3.2

One process instead of a cache, a lock service and a delay queue.

Ruvio speaks RESP, so redis-cli and your existing clients connect. Each shard owns its keys: no lock on the hot path, and the durability you choose at startup.

  • 287 commands
  • 6 first-party clients
  • Linux, macOS and Windows builds
Terminal
$ docker run -d -p 6379:6379 \
    -e RUVIO_REQUIREPASS=change-me \
    salihcantekin/ruvio:linux
$ redis-cli -a change-me
6379> LEASE job:nightly 30000
(integer) 1
6379> LIMIT api:key:42 100 60000
(integer) 99

Performance

Faster than Redis on the commands you run every day

Each bar is one command measured against Redis 8.10.2 on the same machine. Bars outside the dashed ring are faster than Redis; bars inside it are slower.

1.25×geometric mean throughput across 60 commands
57/60commands at 0.97× of Redis or faster
−26%memory across 12 data shapes, 12 of 12 below Redis
287commands, each with a typed method in 6 clients
GEOSEARCH (radius 50 km, COUNT 10): 2.48×SETEX: 2.00×MSET (3 keys): 2.00×GEOADD: 1.74×GETEX (set a 100 s deadline): 1.71×LINSERT (pivot missing, scans 100): 1.53×COPY ... REPLACE (64 B string): 1.51×LMOVE (rotate a 100-element list): 1.50×SET: 1.50×LREM (value missing, scans 100): 1.49×LPOS (100 elements, hit in the middle): 1.47×ZADD (existing member, same score): 1.40×SETBIT (1 KiB bitmap, same bit): 1.40×GETRANGE: 1.37×BITFIELD GET u8: 1.37×HSET (existing field): 1.36×EXPIRE (key with TTL): 1.36×BITFIELD INCRBY u8 (always writes): 1.35×HGET: 1.34×HGET (10-field hash): 1.33×LSET (20-item list): 1.32×OBJECT ENCODING: 1.30×HSET (10-field hash, existing field): 1.30×HSTRLEN: 1.28×RENAME (same key): 1.28×SETRANGE: 1.27×ZSCORE: 1.25×DUMP (64 B string): 1.22×SRANDMEMBER: 1.22×MGET (3 keys): 1.20×HMGET (2 fields): 1.20×PFADD: 1.19×LPUSH: 1.19×HDEL (10-field hash, missing field): 1.17×PERSIST (key without TTL): 1.17×MEMORY USAGE: 1.16×APPEND (1 byte): 1.16×LSET (100 elements): 1.15×PUBLISH (no subscribers): 1.14×ZINCRBY: 1.14×EXISTS: 1.14×SADD (existing member): 1.13×LRANGE 0 2: 1.13×INCRBYFLOAT: 1.12×LINDEX (20-item list): 1.12×SISMEMBER: 1.11×TTL: 1.11×GETBIT (1 KiB bitmap): 1.08×EXPIRETIME: 1.06×PFCOUNT: 1.05×DEL (missing key): 1.05×PTTL: 1.05×TYPE: 1.03×LLEN: 1.00×LRANGE 0 9 (20-item list): 1.00×GET: 0.98×BITCOUNT (1 KiB bitmap): 0.98×HGETALL (10-field hash): 0.97×TOUCH: 0.92×FILLGET (value present): 0.85×0.8×Redis 1×1.5×2×2.5×1.25×geometric mean vs Redis

Largest gains

  1. GEOSEARCH (radius 50 km, COUNT 10)2.48×44K vs 18K ops/s
  2. SETEX2.00×2.50M vs 1.25M ops/s
  3. MSET (3 keys)2.00×2.20M vs 1.10M ops/s
  4. GEOADD1.74×1.94M vs 1.11M ops/s
  5. GETEX (set a 100 s deadline)1.71×2.37M vs 1.38M ops/s

Still behind Redis (3)

  1. HGETALL (10-field hash)0.97×763K vs 789K ops/s
  2. TOUCH0.92×2.31M vs 2.50M ops/s
  3. FILLGET (value present)0.85×1.96M vs 2.31M ops/s
fasterwithin 3%slower

Source: benchmark/records/mac-2026-10-14.json · Apple M4 Pro, 12 cores, 24 GiB, macOS 26.6.2 arm64 · 50 clients, pipeline 16 · in-memory profile, 1 shard · all results

Memory

Less memory for the same data

Memory added by loading each data shape, Ruvio next to Redis on the same machine. Left is less; the axis is logarithmic, so the same saving has the same length at any size.

RuvioRedis
Data shape
2 MiB51020
Saved
100,000 BITFIELD counters (u16)
−51%
100,000 one-element lists
−44%
100,000 strings of 16 B
−42%
50,000 small sets (4 members)
−40%
50,000 small sorted sets (3 members)
−39%
50,000 small hashes (3 fields)
−30%
100,000 geo points in one key
−21%
5,000 HyperLogLogs of 100 elements
−18%
20,000 bitmaps of 128 B (SETBIT)
−16%
100,000 strings of 128 B
−16%
100,000 strings of 16 B with TTL
−12%
20,000 lists of 20 elements
−8%

Source: benchmark/records/mac-2026-10-14.json · Apple M4 Pro, 12 cores, 24 GiB, macOS 26.6.2 arm64

Whole machine

8 shards in one process against Redis on 8 cores

Redis uses one core per process, so a whole machine means a Redis Cluster. Here Ruvio runs 8 shards in one process on 8 cores, against a Redis Cluster of 8 nodes on the same cores and against a single Redis process on one core. Each shard gets the same load as each Redis node.

Every command
1.13×geometric mean throughput against the 8-node Redis Cluster, 59 commands, pipeline 16
1.75× GETand 2.21× SET against the cluster once the client keeps up (pipeline 64): 28.99M and 29.87M req/s
12×the GETs of one Redis process: one core serves 2.34M req/s, the 8 shards 28.99M
0.96×memory per shard against one Redis across 12 data shapes; 1.14× throughput with every write logged (everysec, same as Redis AOF)
Command
500K1.00M2.00M5.00M10.00M
vs cluster
GET
0.96×
SET
1.09×
DEL (missing key)
0.96×
SETEX
1.40×
HSET (existing field)
1.14×
HGET
1.05×
LPUSH
1.02×
ZADD (existing member, same score)
1.16×
EXPIRE (key with TTL)
1.24×
PUBLISH (no subscribers)
1.36×
Ruvio, 8 shards, one processRedis Cluster, 8 nodes, same coresone Redis process, one core

Source: benchmark/records/x86-azure-2026-10-16-8-shards-final.json, pipeline 64 and everysec · Azure F16as_v6, AMD EPYC 9V74, 16 cores · 50 clients per shard, 3 alternating rounds · how it is measured

Architecture

One owner per shard

The shard that owns a key parses, records, applies and replies. The hot path takes no lock and does not hop to another thread, and a feature allocates only when a key uses it.

Shard 0owns its keys
  1. ParseThe connection's shard reads the request.
  2. RecordThe write is checked and appended to the shard's log.
  3. ApplyThe shard changes its own keys. No lock is taken.
  4. ReplyThe answer goes back on the same thread.
Shard 1owns its keys
  1. ParseThe connection's shard reads the request.
  2. RecordThe write is checked and appended to the shard's log.
  3. ApplyThe shard changes its own keys. No lock is taken.
  4. ReplyThe answer goes back on the same thread.
Shard 2owns its keys
  1. ParseThe connection's shard reads the request.
  2. RecordThe write is checked and appended to the shard's log.
  3. ApplyThe shard changes its own keys. No lock is taken.
  4. ReplyThe answer goes back on the same thread.

Durability

Choose how much you can afford to lose

Pick a profile at startup. A write is checked, appended, then applied; if the log cannot take the record, the key stays as it was and later writes are refused.

Recovery guarantees
alwaysfdatasync before the reply

Only a write that was never acknowledged (a torn final record).

everysecsync_interval_ms = 1000

Writes the kernel accepted but had not synced yet.

snapshotsnapshot_interval_ms = 60000

Writes after the last durable checkpoint.

memoryno disk writes

All in-memory state when the process stops.

Bounds from docs/RECOVERY.md; intervals are the defaults in ruvio.example.toml.

Compatibility

287 commands in 21 families, one square each

The command list is checked in CI. Every command has a typed method in all 6 first-party clients, with cluster routing from one seed address.

Command reference
Data typesKeys and keyspaceServer and operations
zsets 30
strings 26
keys 25
hashes 24
json 24
lists 23
streams 21
sets 17
connection 16
keyspace 11
scripts 10
acl 8
geo 8
bloom 8
cluster 8
pubsub 8
bitmap 7
transactions 5
introspection 4
hyperloglog 3
replication 1

Run it next to your app in a minute

One binary or one container. Your Redis client already knows how to talk to it.