A lightweight, thread-safe key-value store built in Rust with async I/O and pub/sub messaging.
┌───────────────────────────────────┐
│ Client (telnet / netcat) │
│ Publisher / Subscriber │
└──────────┬────────────────────────┘
│ TCP (line-based protocol)
▼
┌───────────────────────────────────┐
│ main.rs │
│ Connection Handler │
│ Client ID Assignment │
└──────────┬────────────────────────┘
│ Arc<Db>
▼
┌───────────────────────────────────┐
│ Db │
│ Arc<Mutex<DbInner>> │
│ ├─ Stock (HashMap) │
│ ├─ ChannelManager │
│ └─ Client Subscriptions │
└──────────┬────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ Command Trait │
│ ├─ Get, Set, Del │
│ ├─ Incr, Decr │
│ ├─ Save, Load, Drop │
│ ├─ Publish, Subscribe │
│ ├─ Ttl, Exists │
│ └─ Each implements execute() │
└───────────────────────────────────┘
Single Process Architecture:
When running with --ui, there is ONE process that runs BOTH the TCP server AND the TUI dashboard:
┌─────────────────────────────────────────────────────────────┐
│ Single Process (cargo run -- --ui) │
│ │
│ ┌──────────────────┐ ┌──────────────────────┐ │
│ │ TCP Server │ │ TUI Dashboard │ │
│ │ (port 6379) │ │ (interactive UI) │ │
│ │ │ │ │ │
│ │ • Netcat/telnet │ │ • Key browser │ │
│ │ • redis-cli │ │ • Command input │ │
│ │ • Other clients │ │ • History view │ │
│ └────────┬─────────┘ └──────────┬───────────┘ │
│ │ │ │
│ │ Arc<Db> │ │
│ │ (shared reference) │ │
│ │ │ │
│ └──────────────┬───────────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Arc<Mutex< │ │
│ │ DbInner │ │
│ │ > │ │
│ │ │ │
│ │ ├─ Stock │ │
│ │ ├─ ChannelMgr │ │
│ │ └─ ClientSubs │ │
│ └─────────────────┘ │
│ In-Memory Database │
└─────────────────────────────────────────────────────────────┘
│
│ TCP Protocol (line-based)
▼
┌───────────────────────────┐
│ External Clients │
│ (netcat, telnet, etc.) │
└───────────────────────────┘
Key Points:
- The TUI is NOT a client - It accesses the database directly in memory
- The TUI does NOT connect via TCP - It shares the same
Arc<Db>reference - External clients connect via TCP - On port 6379 (same as Redis default)
- Both share the same data - Changes from TUI or external clients are instantly visible to both
Two Modes:
- Server-only mode (
cargo run): Just the TCP server, no TUI - Combined mode (
cargo run -- --ui): TCP server + TUI dashboard in one process
| Module | Description |
|---|---|
main.rs |
TCP server, connection handling, client ID assignment, graceful shutdown |
db.rs |
Database layer with interior mutability pattern (Arc<Mutex>) |
command.rs |
Command trait definition and re-exports |
commands/ |
Individual command implementations (Get, Set, Del, etc.) |
request.rs |
Request parsing (GET, SET, DEL, EXISTS, INCR, DECR, SAVE, LOAD, DROP, PUB, SUB, UNSUB, TTL) |
stock.rs |
Key-value storage with expiration support and JSON persistence |
channel_manager.rs |
Pub/sub channel management with broadcast channels |
returns.rs |
Return types (Ok, Err, NotFound, Subscribe, Unsubscribe) |
- GET/SET/DEL - Basic key-value store operations
- EXISTS - Check if multiple keys exist
- SET EXP - Key expiration with TTL parameter
- TTL - Get remaining time to live for a key
- INCR/DECR - Atomic increment and decrement for counters
- DROP - Clear all data from the store
- ASYNC SERVER - Multi-threaded async server with Tokio runtime
- SAVE/LOAD - JSON-based persistence to file
- GRACEFUL SHUTDOWN - Signal handling for clean Ctrl+C termination
- PUB/SUB/UNSUB - Pub/Sub messaging with broadcast channels
- CLIENT TRACKING - Connection tracking and cleanup
See FEATURES.md for the full roadmap with planned features.
# Build and run (TCP server mode)
cargo run
# The server starts on 127.0.0.1:6379
# Run with TUI dashboard:
cargo run -- --ui1. TCP Server Mode (Default)
cargo run
# Starts TCP server on port 6379
# Use with telnet/netcat2. TUI Dashboard Mode
cargo run -- --ui
# Starts TCP server + Interactive dashboard
# Perfect for testing and monitoringIn another terminal:
nc localhost 6379
# or
telnet localhost 6379| Command | Description | Example |
|---|---|---|
GET <key> |
Retrieve a value by key | GET mykey |
SET <key> <value> [EXP <sec>] |
Set a key-value pair with optional expiration | SET mykey hello EXP 10 |
DEL <key> |
Delete a key | DEL mykey |
EXISTS <key> [<key> ...] |
Check if one or more keys exist | EXISTS key1 key2 key3 |
TTL <key> |
Get remaining time to live for a key (in ms) | TTL mykey |
INCR <key> |
Increment value by 1 (creates key with value 1 if not exists) | INCR counter |
DECR <key> |
Decrement value by 1 (creates key with value -1 if not exists) | DECR counter |
SAVE <file.json> |
Save state to ./data/<file.json> |
SAVE dump.json |
LOAD <file.json> |
Load state from ./data/<file.json> |
LOAD dump.json |
DROP |
Clear all keys | DROP |
PUB <channel> <message> |
Publish a message to a channel | PUB news Hello World |
SUB <channel> |
Subscribe to a channel | SUB news |
UNSUB <channel> |
Unsubscribe from a channel | UNSUB news |
Expiration: TTL in seconds. Expired keys are removed lazily on GET, TTL, or EXISTS.
EXISTS: Returns existence status for each key in format key -> true/false.
Pub/Sub: Each client can subscribe to one channel at a time. Messages are broadcast to all subscribers.
Interactive Dashboard: Run with --ui flag for a beautiful TUI interface with real-time key monitoring, command history, and live updates.
# Start with interactive dashboard
cargo run -- --uiTab 1: Key-Value Store
- Real-time list of all keys and values
- TTL countdown timers for expiring keys
- Navigate with arrow keys
Tab 2: Command History
- See all executed commands
- Color-coded results
Tab 3: Help
- Quick reference for all commands
| Key | Action |
|---|---|
q |
Quit dashboard |
i |
Enter command mode |
Esc |
Exit command mode |
Enter |
Execute command |
| arrows | Navigate keys |
Tab |
Next tab |
Main Dashboard (Keys View):
History Tab:
Key Features:
- ✅ Real-time key updates (refresh every 250ms)
- ✅ Color-coded command results (cyan=input, green=success, red=error)
- ✅ TTL countdown with visual indicator for expiring keys
- ✅ Responsive keyboard navigation
- ✅ Works alongside TCP server on port 6379
SET username alice
OK
GET username
alice
SET temp data EXP 3000
OK
TTL temp
3000
GET temp
data
# (after 3 seconds)
GET temp
Key 'temp' not found
SAVE mydata.json
OK
LOAD mydata.json
OK
INCR views
1
INCR views
2
INCR views
3
DECR views
2
SET counter 10
OK
INCR counter
11
DECR counter
10
SET key1 value1
OK
SET key2 value2
OK
EXISTS key1 key2 key3
key1 -> true
key2 -> true
key3 -> false
EXISTS key1
key1 -> true
DEL key1
OK
EXISTS key1 key2
key1 -> false
key2 -> true
Terminal 1 (Subscriber):
SUB news
Subscribed
MESSAGE news Breaking: Rust 2.0 released!
MESSAGE news Update: Performance improvements
UNSUB news
Unsubscribed
Terminal 2 (Publisher):
PUB news Breaking: Rust 2.0 released!
Published to 1 subscriber(s)
PUB news Update: Performance improvements
Published to 1 subscriber(s)
UNSUB news
Error: Not subscribed to any channel
SUB news
Subscribed
UNSUB sports
Error: Not subscribed to channel 'sports'
UNSUB nonexistent
Error: Channel 'nonexistent' does not exist
struct Data {
value: String,
expiration: Option<u64>, // Unix timestamp in milliseconds
}
struct Stock {
map: HashMap<String, Data>,
}- Arc with interior mutability -
DbwrapsArc<Mutex<DbInner>>for efficient cloning - Command trait pattern - Each command implements
execute(&self, db: &Arc<Db>, client_id: u64) - Tokio tasks - Each client spawns an async task
- Client IDs -
AtomicU64counter for unique IDs - Broadcast channels -
tokio::sync::broadcastfor pub/sub (capacity: 16)
Commands are implemented using the Command trait pattern:
// Each command is a separate struct
pub struct Get { pub key: String }
pub struct Set { pub key: String, pub value: String, pub expiration: Option<u64> }
// Command trait defines execution
pub trait Command {
fn execute(&self, db: &Arc<Db>, client_id: u64) -> Return;
}
// Request parses and converts to Command
impl Request {
pub fn into_command(self) -> Box<dyn Command> {
match self {
Request::GET(key) => Box::new(Get { key }),
// ...
}
}
}Benefits:
- Easy to add new commands (just implement Command trait)
- Self-contained command logic
- Clean separation of concerns
- Follows idiomatic Rust patterns
- Lazy expiration - Keys are checked and removed on
GET,TTL, orEXISTS - No background cleanup - Expired keys remain in memory until accessed
Publisher Db (Arc<Mutex<DbInner>>) Subscriber
│ │ │
│ PUB channel msg │ │
│ ────────────────────────────►│ │
│ Request::parse() │ │
│ .into_command() │ │
│ command.execute(&db) │ │
│ │ broadcast::send() │
│ │ ─────────────────────────►│
│ │ │ MESSAGE ...
| Parameter | Default | Description |
|---|---|---|
| Bind address | 127.0.0.1:6379 |
Hardcoded in main.rs |
| Broadcast capacity | 16 | Buffer size per channel |
| Crate | Usage |
|---|---|
tokio |
Async runtime, TCP, sync primitives |
serde |
Serialization |
serde_json |
JSON persistence |
src/
├── main.rs # Server entry point, connection handling
├── db.rs # Database layer (Arc<Mutex<DbInner>>)
├── command.rs # Command trait definition
├── commands/ # Individual command implementations
│ ├── mod.rs # Module exports
│ ├── get.rs # GET command
│ ├── set.rs # SET command
│ ├── del.rs # DEL command
│ ├── incr.rs # INCR command
│ ├── decr.rs # DECR command
│ ├── save.rs # SAVE command
│ ├── load.rs # LOAD command
│ ├── drop.rs # DROP command
│ ├── publish.rs # PUB command
│ ├── subscribe.rs # SUB command
│ ├── unsubscribe.rs # UNSUB command
│ ├── ttl.rs # TTL command
│ └── exists.rs # EXISTS command
├── request.rs # Request parsing and conversion to Command
├── stock.rs # Key-value storage with expiration
├── channel_manager.rs # Pub/sub channel management
├── returns.rs # Return types
├── ui/ # Terminal UI dashboard
│ ├── mod.rs # UI module exports
│ ├── app.rs # Application state
│ └── ui.rs # TUI rendering and event handling
└── lib.rs # Library exports
Current Architecture (Command Trait Pattern):
Dbwith interior mutability (Arc<Mutex<DbInner>>)- Separate command files in
commands/directory - Each command implements
Commandtrait Arc<Db>passed to each command'sexecute()method- Extensible: add new command by creating struct + implementing trait
| Crate | Usage |
|---|---|
tokio |
Async runtime, TCP, sync primitives |
serde |
Serialization |
serde_json |
JSON persistence |
ratatui |
Terminal UI dashboard |
crossterm |
Terminal manipulation |
See FEATURES.md for planned and completed features.
Licensed under MIT

