1 stable release

new 1.0.3 Mar 16, 2025
0.1.0 Mar 15, 2025

#322 in Database interfaces

Download history 211/week @ 2025-03-12

211 downloads per month

Apache-2.0

75KB
1.5K SLoC

πŸ† Zyst

GitHub release (latest SemVer) build status dependency status

Zyst is a Redis-compatible server reimplemented from scratch in Rust. It aims to provide a in-memory key-value store while leveraging Rust’s safety and concurrency features. Zyst is only supported on Linux.

I make this project for fun, it doesn't aim to replace Redis. For now, I am focusing on implementing common Redis functionalities rather than on performance.

Flamegraph

Install

Docker:

docker run -d --name zyst -p 6379:6379 bourdeau/zyst:latest

Cargo:

cargo install zyst

In another terminal:

redis-cli -h 127.0.0.1 -p 6379
SET first_name John

Features

Zyst implements Append-Only File (AOF) system. AOF persistence logs every write operation received by the server, allowing these operations to be replayed during startup to restore the original dataset.

Commands

Key-Value Storage

Command Syntax Example Output Done
SET SET key value SET user:1 "John" OK βœ…
GET GET key GET user:1 "John" βœ…
DEL DEL key DEL user:1 1 (if key existed) βœ…
EXISTS EXISTS key EXISTS user:1 1 (exists) / 0 (not) βœ…

Expiration & Time-to-Live

Note: expired keys are deleted by calling GET or by a background task every 60 seconds.

Command Syntax Example Output Done
EXPIRE EXPIRE key seconds EXPIRE user:1 3600 1 (success) βœ…
TTL TTL key TTL user:1 3599 (seconds left) βœ…

Counters & Rate Limiting

Command Syntax Example Output Done
INCR INCR key INCR api:requests 1, 2, 3... βœ…
DECR DECR key DECR api:requests 2, 1, 0... βœ…
INCRBY INCRBY key amount INCRBY api:requests 5 5, 10, 15... βœ…

Lists

Command Syntax Example Output Done
LPUSH LPUSH key values LPUSH queue task1 task2 task3 1 (new length) βœ…
LRANGE LRANGE key start stop LRANGE queue 0 -1 2 (new length) βœ…
RPUSH RPUSH key value RPUSH queue "task2" 2 (new length) βœ…
LPOP LPOP key LPOP queue "task1" βœ…
RPOP RPOP key RPOP queue "task2" βœ…

Hashes

Command Syntax Example Output Done
HSET HSET key field value HSET user:1 name "Alice" 1 βœ…
HGET HGET key field HGET user:1 name "Alice" βœ…
HDEL HDEL key field HDEL user:1 name 1 βœ…
HGETALL HGETALL key HGETALL user:1 ["name", "Alice"] βœ…

Sets

Command Syntax Example Output Done
SADD SADD key value SADD online_users "user1" 1 βœ…
SREM SREM key value SREM online_users "user1" 1 βœ…
SMEMBERS SMEMBERS key SMEMBERS online_users ["user2", "user3"] βœ…

Miscellaneous

Command Syntax Example Output Done
FLUSHDB FLUSHDB FLUSHDB OK βœ…
FLUSHALL FLUSHALL FLUSHALL OK
KEYS KEYS pattern KEYS user:* ["user:1", "user:2"] βœ…

Benchmark

On average, Zyst is 15% slower than Redis, which came as a surprise, as I was expecting much worse performance conzysting I almost didn't make any optimizations.

On my machine:

OS: NixOS 24.11 (Vicuna)
KERNEL: 6.6.74 
CPU: AMD Ryzen 7 9700X 8-Core
RAM: 31 GB

Redis 7.2.7:

➜ redis-benchmark -t set,get,incr,lpush,rpush,lpop,rpop,hset,lpush,lrange_100,lrqnge_300,lrange_500,lrange_600 -n 100000 -q
SET: 306748.47 requests per second, p50=0.087 msec
GET: 367647.03 requests per second, p50=0.071 msec
INCR: 367647.03 requests per second, p50=0.071 msec
LPUSH: 369003.69 requests per second, p50=0.071 msec
RPUSH: 369003.69 requests per second, p50=0.071 msec
LPOP: 371747.22 requests per second, p50=0.071 msec
RPOP: 373134.31 requests per second, p50=0.071 msec
HSET: 371747.22 requests per second, p50=0.071 msec
LPUSH (needed to benchmark LRANGE): 374531.84 requests per second, p50=0.071 msec
LRANGE_100 (first 100 elements): 232018.56 requests per second, p50=0.111 msec
LRANGE_500 (first 500 elements): 69832.40 requests per second, p50=0.359 msec
LRANGE_600 (first 600 elements): 59594.76 requests per second, p50=0.415 msec

Zyst 0.6.0:

redis-benchmark -t set,get,incr,lpush,rpush,lpop,rpop,hset,lpush,lrange_100,lrqnge_300,lrange_500,lrange_600 -n 100000 -q
SET: 264550.28 requests per second, p50=0.103 msec
GET: 349650.34 requests per second, p50=0.079 msec
INCR: 307692.31 requests per second, p50=0.087 msec
LPUSH: 304878.03 requests per second, p50=0.087 msec
RPUSH: 303951.38 requests per second, p50=0.087 msec
LPOP: 306748.47 requests per second, p50=0.087 msec
RPOP: 292397.66 requests per second, p50=0.087 msec
HSET: 304878.03 requests per second, p50=0.087 msec
LPUSH (needed to benchmark LRANGE): 303030.28 requests per second, p50=0.087 msec
LRANGE_100 (first 100 elements): 205761.31 requests per second, p50=0.127 msec
LRANGE_500 (first 500 elements): 63211.12 requests per second, p50=0.423 msec
LRANGE_600 (first 600 elements): 54229.93 requests per second, p50=0.495 msec

Dependencies

~10–20MB
~270K SLoC