not_redis is a Redis-compatible in-memory data structure library written in Rust. It provides Redis-like APIs without the networking overhead, external service dependencies, or operational complexity of running a Redis server.
Key Goals:
- Zero-config, embeddable Redis-compatible storage
- Thread-safe concurrent access via Tokio and DashMap
- RESP-compatible data types and command semantics
- No network overhead - runs in-process with your application
- Minimal dependencies for security and simplicity
- Strings: GET, SET, DEL, EXISTS, EXPIRE, TTL, PERSIST
- Hashes: HSET, HGET, HGETALL, HDEL
- Lists: LPUSH, RPUSH, LLEN
- Sets: SADD, SMEMBERS
- Utilities: PING, ECHO, DBSIZE, FLUSHDB
[dependencies]
not_redis = "0.1"use not_redis::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = Client::new();
client.start().await;
// String operations
client.set("user:1:name", "Alice").await?;
let name: String = client.get("user:1:name").await?;
println!("Name: {}", name);
// Hash operations
client.hset("user:1", "email", "alice@example.com").await?;
client.hset("user:1", "age", "30").await?;
let email: String = client.hget("user:1", "email").await?;
let profile: Vec<String> = client.hgetall("user:1").await?;
println!("Email: {}", email);
// List operations
client.lpush("user:1:todos", "buy milk").await?;
client.lpush("user:1:todos", "walk dog").await?;
let count: i64 = client.llen("user:1:todos").await?;
println!("Todos: {}", count);
// Set operations
client.sadd("user:1:tags", "rust").await?;
client.sadd("user:1:tags", "developer").await?;
let tags: Vec<String> = client.smembers("user:1:tags").await?;
println!("Tags: {:?}", tags);
// Expiration
client.set("temp:key", "expires soon").await?;
client.expire("temp:key", 60).await?;
let ttl: i64 = client.ttl("temp:key").await?;
println!("TTL: {} seconds", ttl);
// Utilities
let pong: String = client.ping().await?;
println!("Ping: {}", pong);
let size: i64 = client.dbsize().await?;
println!("DB size: {}", size);
// Cleanup
let _: String = client.flushdb().await?;
Ok(())
}not_redis requires a Tokio runtime. If you're using it in a non-Tokio context:
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = Client::new();
client.start().await;
// ... your code ...
}let mut client = Client::new();
client.start().await;| Method | Description |
|---|---|
get(key) |
Get value by key |
set(key, value) |
Set key-value pair |
del(key) |
Delete key, returns count |
exists(key) |
Check if key exists |
expire(key, seconds) |
Set key expiration |
ttl(key) |
Get remaining TTL (-2=missing, -1=no expiry, >=0=seconds) |
persist(key) |
Remove expiration, returns success |
flushdb() |
Clear all keys |
| Method | Description |
|---|---|
hset(key, field, value) |
Set hash field |
hget(key, field) |
Get hash field |
hgetall(key) |
Get all fields/values |
hdel(key, field) |
Delete hash field |
| Method | Description |
|---|---|
lpush(key, value) |
Push to list head |
rpush(key, value) |
Push to list tail |
llen(key) |
Get list length |
| Method | Description |
|---|---|
sadd(key, member) |
Add to set |
smembers(key) |
Get all members |
| Method | Description |
|---|---|
ping() |
Returns "PONG" |
echo(msg) |
Echo message |
dbsize() |
Number of keys |
not_redis uses DashMap for thread-safe concurrent access. Multiple threads can share a single Client instance.
use std::sync::Arc;
use not_redis::Client;
let client = Arc::new(Client::new());
let client_clone = client.clone();
tokio::spawn(async move {
client_clone.set("key", "value").await.unwrap();
});use not_redis::{Client, RedisError};
match client.get("nonexistent").await {
Ok(value) => println!("Found: {}", value),
Err(RedisError::NoSuchKey(key)) => println!("Key not found: {}", key),
Err(RedisError::WrongType) => println!("Wrong data type"),
Err(RedisError::ParseError) => println!("Parse error"),
Err(e) => println!("Other error: {:?}", e),
}| Feature | Redis | not_redis |
|---|---|---|
| Setup | Requires Redis server | No setup needed |
| Network | TCP/IP overhead | In-process, zero-copy |
| Operations | Requires redis-cli or client | Direct API calls |
| Deployment | Additional service | Single binary |
- No persistence (data lost on restart)
- No networking layer
- No clustering or replication
- Limited command set (growing)
- No Lua scripting
- No pub/sub
Benchmarks were run on Apple Silicon (M1) with not_redis and Redis (via redis-rs) to compare performance. Redis was running in a Docker container on the same machine.
| Operation | not_redis | Redis | Difference |
|---|---|---|---|
| string/set | 360,000 ops/s | 1,300 ops/s | +27,646% |
| string/get | 356,000 ops/s | 360 ops/s | +98,889% |
| hash/hset | 348,000 ops/s | 1,250 ops/s | +27,740% |
| hash/hget | 319,000 ops/s | 1,400 ops/s | +22,771% |
| list/lpush | 358,000 ops/s | N/A | - |
| list/rpush | 362,000 ops/s | N/A | - |
| set/sadd | 351,000 ops/s | N/A | - |
| Operation | not_redis | Redis | Difference |
|---|---|---|---|
| batch_writes/100 | 4.6M ops/s | N/A | - |
| batch_writes/1000 | 5.2M ops/s | N/A | - |
| batch_reads/100 | 3.1M ops/s | N/A | - |
| batch_reads/1000 | 3.3M ops/s | N/A | - |
not_redis significantly outperforms Redis for in-process operations because:
- Zero network overhead - not_redis runs in the same process, eliminating TCP/IP communication
- No serialization/deserialization - Direct memory access vs Redis protocol parsing
- No connection management - No connection pooling overhead
- Cache-friendly - Data stays in-process, maximizing CPU cache utilization
For applications that don't need Redis's networking capabilities, not_redis provides substantial performance improvements while offering a compatible API.
Issues and PRs welcome. Note: This is a vibe-coded project - expect quirks.
MIT