tiny-redis is a lightweight, asynchronous Redis server implementation written from scratch in Rust. It is designed to explore the core concepts of Redis, including its network protocol (RESP), master-slave replication, data persistence (RDB), and common data structures.
The project leverages the power of Tokio for high-performance, non-blocking I/O, allowing it to handle multiple concurrent client connections efficiently.
- Asynchronous Architecture: Built entirely on
async/awaitand the Tokio runtime, ensuring high throughput and concurrency with minimal overhead. - Master-Slave Replication: Full support for master-slave replication, including the
PSYNChandshake, command propagation from master to replicas, and theWAITcommand for checking replication consistency. - RDB Persistence: Capable of parsing Redis's RDB file format on startup to load a persisted dataset into memory.
- RESP Protocol Handling: A robust parser for the Redis Serialization Protocol (RESP), capable of handling simple strings, bulk strings, integers, arrays, and errors.
- Concurrent State Management: Thread-safe access to the central key-value store and server state using
Arc<Mutex<T>>for safe sharing across concurrent tasks. - Key Expiry & Cleanup: A dedicated background task periodically cleans up expired keys from the cache, mimicking Redis's own active expiration strategy.
- Blocking Operations: Correctly implements blocking commands like
BLPOPandXREAD BLOCKusing Tokio's notification mechanisms (tokio::sync::Notify) to efficiently suspend and resume client tasks without blocking threads. - Publish/Subscribe: A complete Pub/Sub implementation that allows clients to subscribe to channels and receive messages published by other clients.
- Transactions: Supports
MULTI,EXEC, andDISCARDfor atomic execution of a block of commands.
The server implements a wide range of Redis commands across several categories:
| Command | Category | Description |
|---|---|---|
PING |
Generic | Returns "PONG", used to test connection. |
ECHO |
Generic | Returns the given string. |
GET |
Generic | Get the value of a key. |
SET |
Generic | Set the value of a key, with support for NX, XX, EX, PX, GET. |
TYPE |
Generic | Returns the string representation of the type of the value stored at key. |
KEYS |
Generic | Find all keys matching the given pattern. |
INCR |
Generic | Increments the integer value of a key by one. |
CONFIG GET |
Generic | Get configuration parameters. |
INFO |
Generic | Get information and statistics about the server (specifically replication). |
RPUSH / LPUSH |
List | Append/prepend one or multiple values to a list. |
LRANGE |
List | Get a range of elements from a list. |
LLEN |
List | Get the length of a list. |
LPOP |
List | Remove and get the first elements in a list. |
BLPOP |
List | Remove and get the first element in a list, or block until one is available. |
XADD |
Stream | Appends a new entry to a stream. |
XRANGE |
Stream | Return a range of entries in a stream. |
XREAD |
Stream | Read entries from one or more streams, optionally blocking. |
PSYNC |
Replication | Used by replicas to initiate synchronization with a master. |
REPLCONF |
Replication | Used to configure a replica or receive acknowledgements (ACK). |
WAIT |
Replication | Wait for a specified number of replicas to acknowledge a write. |
MULTI |
Transaction | Marks the start of a transaction block. |
EXEC |
Transaction | Executes all commands queued in a transaction. |
DISCARD |
Transaction | Flushes all commands queued in a transaction. |
SUBSCRIBE |
Pub/Sub | Subscribes the client to the given channels. |
UNSUBSCRIBE |
Pub/Sub | Unsubscribes the client from the given channels. |
PUBLISH |
Pub/Sub | Posts a message to a channel. |
ZADD |
Sorted Set | Adds a member with a score to a sorted set. |
ZRANGE |
Sorted Set | Returns a range of members from a sorted set. |
ZRANK |
Sorted Set | Returns the rank of a member in a sorted set. |
ZSCORE |
Sorted Set | Returns the score of a member in a sorted set. |
ZCARD |
Sorted Set | Returns the cardinality of a sorted set. |
ZREM |
Sorted Set | Removes a member from a sorted set. |
GEOADD |
Geospatial | Adds a geospatial item (longitude, latitude, name). |
GEOPOS |
Geospatial | Returns the longitude and latitude of members. |
GEODIST |
Geospatial | Returns the distance between two members. |
GEOSEARCH |
Geospatial | Queries a sorted set of geospatial items by radius. |
-
Tokio for Asynchronous I/O: The entire server is built on the Tokio runtime. The main thread runs an accept loop, and each new TCP connection is spawned as a separate, lightweight green thread (a
tokio::task). This allows the server to handle thousands of concurrent connections on a small number of OS threads. -
Shared State with
Arc<Mutex<T>>: The central cache, server configuration, and replication state are wrapped inArc<Mutex<T>>. This standard Rust pattern enables safe, shared access to data from the many concurrent client-handling tasks. Locks are held for the shortest duration possible to maximize throughput. -
Enum-based Server Role (
RedisServer::Master/Slave): The server's role is determined at startup and encapsulated in an enum. This leverages Rust's type system to provide compile-time guarantees about which state and logic is available, avoiding runtime errors. Both variants implement a commonCommandHandlertrait, allowing for clean, polymorphic command execution. -
Command Pattern with
RedisCommandEnum: Incoming RESP commands are parsed into a structuredRedisCommandenum. This decouples the network parsing logic from the command execution logic. Theexecutefunction uses a singlematchstatement to delegate to the appropriate handler, making the code clean, readable, and easy to extend with new commands. -
Actor-like Tasks for Background Processes: Long-running, isolated tasks are modeled as actors. For example:
- A Replication Task listens on an MPSC channel for
ReplicationMsgs (likeBroadcast) and propagates commands to all connected replicas. - A Pub/Sub Task listens on a channel for
PubSubMsgs and broadcasts messages to all clients subscribed to a given channel. - A Cleanup Task runs on a timer (
tokio::time::interval) to periodically scan and remove expired keys. This design isolates concerns and prevents any single part of the system from blocking others.
- A Replication Task listens on an MPSC channel for