A lightweight Redis protocol-compatible server implemented in Rust for learning and experimentation.
This project implements a subset of Redis commands and features focused on strings, streams, replication handshakes, and RDB loading. It's intended as an educational/simple Redis clone rather than a production replacement for Redis.
-
Redis RESP protocol server listening on TCP (default port 6379).
-
Supported commands (partial compatibility):
- PING — returns PONG
- ECHO — echoes back the message
- SET [PX ] — set string values with optional millisecond TTL
- GET — get string value (respecting expiry)
- CONFIG GET dir|dbfilename — read basic config values
- KEYS * — list all keys (filters expired keys)
- INFO [replication] — basic INFO output (role/master/replication fields)
- WAIT — wait for write acknowledgements from replicas
- TYPE — returns the type of value (string or stream)
- XADD field value [field value ...] — append entry to a stream (creates stream if missing). Supports auto-generated IDs using
*
and ID validation. - XRANGE — iterate a stream range
- XREAD [BLOCK ] STREAMS <...> <...> — blocking and non-blocking stream reads
- PSYNC / REPLCONF / REPL-related commands used for master <-> replica handshake and offset/ACK behavior
-
RDB file parsing and initial loading at startup (basic subset of RDB supported). The loader reads string values and expiry metadata from an RDB file.
-
Simple replication support:
--replicaof <host> <port>
(flag) lets the server act as a replica and perform basic PSYNC / FULLRESYNC flow.- Master-side logic writes write-commands to connected replicas and tracks a master offset. Replicas respond with REPLCONF ACK to indicate progress; WAIT uses this info.
The server accepts the following command-line options:
--dir <path>
— directory used to locate the RDB file (default.
)--dbfilename <name>
— name of the RDB file to load at startup (defaultdump.rdb
)--port <port>
— TCP port the server binds to (default6379
)--replicaof "<host> <port>"
— become a replica of the specified master (example:--replicaof "127.0.0.1 6379"
)
Examples:
# Run server on default port and load ./dump.rdb
cargo run --release
# Run on port 6380 and use a custom rdb path
cargo run -- --port 6380 --dir ./data --dbfilename mydump.rdb
# Run as replica (connects to master and performs a simple PSYNC/FULLRESYNC)
cargo run -- --replicaof "127.0.0.1 6379"
You can use a standard Redis client (for example redis-cli
) to talk to this server because it speaks RESP.
# ping
redis-cli -p 6379 PING
# set with PX (milliseconds TTL)
redis-cli -p 6379 SET mykey myvalue PX 5000
# get
redis-cli -p 6379 GET mykey
# stream: add entries
redis-cli -p 6379 XADD mystream * field1 val1 field2 val2
# range over stream
redis-cli -p 6379 XRANGE mystream - +
# xread blocking (wait up to 5s for new entries)
redis-cli -p 6379 XREAD BLOCK 5000 STREAMS mystream 0
# replication: wait for 1 replica ack (timeout 2000ms)
redis-cli -p 6379 WAIT 1 2000
- Language & runtime: Rust (single binary). Uses standard library networking primitives (
TcpListener
,TcpStream
) and synchronization primitives (Arc
,Mutex
,RwLock
). - Data model: in-memory HashMap keyed by
String
with a customValueEntry
that stores either aString
value or aStream
(vector of stream entries), plus optional expiry timestamps. - Streams: implemented with an internal
StreamEntry
structure containing an ID and a sequence of field/value pairs. Supports auto-generated IDs with*
and basic ID validation (prevents adding an ID <= last ID). - Persistence: basic RDB parsing and load at startup. The RDB parser implements a subset of the RDB formats: string values, expiries (seconds and milliseconds), and some metadata. It does NOT implement LZF-compressed strings.
- Replication & offsets: master writes commands payloads to connected replicas and increments an in-memory master offset. Replicas respond to
REPLCONF ACK
and the server retainsReplicaInfo
with offsets and last ack time.WAIT
checks these offsets to compute acknowledgement counts. - Concurrency: each accepted TCP client runs in its own std::thread; shared server state is protected by
Arc<Mutex<...>>
orArc<RwLock<...>>
as appropriate.