In-memory key-value store written in pure Zig — Redis-compatible, zero dependencies, single binary.
- Redis Protocol — RESP2 compatible, works with
redis-cli - Zero Dependencies — 100% pure Zig, no C libraries, no external packages
- Single Binary — Everything in one statically-linked executable
- TTL Support — Keys expire automatically (lazy, on access)
- String / List / Hash / Set types — RESP2-encoded, works with
redis-cli - Fast — single-threaded non-blocking epoll loop, zero-alloc reply writing (near-parity with Redis on SET)
- Embeddable — in-process Zig module (
@import("zigkv").Store) or run as a standalone server
git clone https://github.com/sudo-su-coffee/zigkv
cd zigkv
zig build -Doptimize=ReleaseFast
# Binary at ./zig-out/bin/zigkv# Linux x86_64
curl -L https://github.com/sudo-su-coffee/zigkv/releases/latest/download/zigkv-linux-x86_64 -o zigkv
chmod +x zigkv
# macOS
curl -L https://github.com/sudo-su-coffee/zigkv/releases/latest/download/zigkv-macos-arm64 -o zigkv
chmod +x zigkv# Start server on default port 6379
zigkv server
# Custom port
zigkv server 6380# Works with any Redis client
redis-cli SET mykey "hello"
redis-cli GET mykey
# → "hello"
redis-cli INCR counter
redis-cli INCR counter
# → 2
redis-cli EXPIRE temp 60
redis-cli TTL temp
# → 58Batch-mode NDJSON input (zigkv batch) and JSON snapshot save/load
(zigkv save / zigkv load) are on the roadmap but not implemented in
this codebase. The binary today exposes only zigkv server [port] and
zigkv ping.
zig build -Doptimize=ReleaseFast
./bench.sh 1000000 50 # requests, connectionsbench.sh drives both zigkv and a real redis-server with redis-benchmark
under identical settings and prints a markdown comparison table. (There is no
zigkv bench subcommand.)
How it works: bench.sh starts real redis-server and zigkv on separate
ports and drives both with the same redis-benchmark workload (identical
requests/connections over the RESP2 protocol), writing raw outputs + a
comparison table to bench-results/. There is no zigkv bench subcommand --
the binary is 100% pure Zig with no third-party C libraries. Reproduce any time:
zig build -Doptimize=ReleaseFast
sudo apt install redis-server redis-tools # if you don't have them
./bench.sh 1000000 50 # requests, connections| Component | Spec |
|---|---|
| CPU | Intel Core i5-1135G7 @ 2.40 GHz (11th-gen Tiger Lake) |
| Cores / threads | 4 / 8 |
| RAM | 16 GB |
| OS | Windows 11 host, benchmarked inside WSL2 Kali Linux |
| Build | -Doptimize=ReleaseFast, pure Zig (no C/3rd-party deps) |
| Operation | zigkv (ops/sec) | Redis (ops/sec) | zigkv vs Redis |
|---|---|---|---|
| SET | 63,869 | 64,800 | 0.99x |
| GET | 62,208 | 64,633 | 0.96x |
| INCR | 81,840 | 62,523 | 1.31x |
Run on the box above, not dedicated hardware. Absolute throughput swings widely -- a prior run of the same binaries measured ~120k ops/sec on both sides while this run shows ~63k (thermal / virtualization load) -- so trust the ratios, not the raw counts. The stable signal is that zigkv holds parity with Redis on SET/GET and now beats it on INCR. The like-for-like improvement against the earlier build of zigkv on this box:
| Operation | previous zigkv | now | change |
|---|---|---|---|
| SET | 89,590 | 119,732 | +34% |
| GET | 91,199 | 118,203 | +30% |
| INCR | 92,764 | 119,660 | +29% |
The speed passes (direct-to-buffer RESP writing, first-byte dispatch,
allocator reuse in SET/APPEND/INCR, then single-allocation entries + edge
triggered epoll) lifted zigkv ~30% against the earlier build and pushed INCR
past real Redis (0.90x -> 1.31x). Re-run bench.sh a few times on your own
hardware before trusting any single number, including these.
Architecture: single-threaded, non-blocking epoll event loop (the same
architecture family Redis itself uses) with TCP_NODELAY on every accepted
connection and zero-allocation reply writing in the command layer.
zigkv exposes its store as an in-process Zig module (src/root.zig), so you can
skip the network server entirely and use it as your app's in-memory KV:
# in your build.zig:
# _ = b.addModule("zigkv", .{ .root_source_file = b.path("src/root.zig") });
# then declare `zigkv` in build.zig.zon and:
# const zk = b.dependency("zigkv", .{}).module("zigkv");const zk = @import("zigkv");
var store = zk.Store.init(std.heap.smp_allocator);
defer store.deinit();
try store.set("session:1", "data", null);
std.log.info("got {s}", .{store.getRef("session:1").?});No socket, no Redis, no network hop -- just an allocator. Useful when the TCP server is overkill and you want a single-binary in-memory store inside your own process.
| Command | Syntax | Description |
|---|---|---|
SET |
SET key value [TTL seconds] |
Store a key-value pair |
GET |
GET key |
Retrieve a value |
DEL |
DEL key |
Delete a key |
INCR |
INCR key |
Increment integer value by 1 |
DECR |
DECR key |
Decrement integer value by 1 |
EXPIRE |
EXPIRE key seconds |
Set expiration time |
TTL |
TTL key |
Get remaining time to live |
PING |
PING |
Test connectivity |
| Command | Syntax | Description |
|---|---|---|
LPUSH |
LPUSH key value [value ...] |
Push value(s) to the left of a list |
RPUSH |
RPUSH key value [value ...] |
Push value(s) to the right of a list |
LPOP |
LPOP key |
Pop from the left |
RPOP |
RPOP key |
Pop from the right |
LLEN |
LLEN key |
List length |
LRANGE |
LRANGE key start stop |
Range slice (Redis-style negative indices) |
| Command | Syntax | Description |
|---|---|---|
HSET |
HSET key field value [field value ...] |
Set one or more hash fields |
HGET |
HGET key field |
Get a hash field |
HGETALL |
HGETALL key |
Get all fields and values |
HDEL |
HDEL key field |
Delete a hash field |
HEXISTS |
HEXISTS key field |
Check if a field exists |
| Command | Syntax | Description |
|---|---|---|
SADD |
SADD key member [member ...] |
Add member(s) to a set |
SREM |
SREM key member [member ...] |
Remove member(s) |
SMEMBERS |
SMEMBERS key |
List all members |
SISMEMBER |
SISMEMBER key member |
Check membership |
SCARD |
SCARD key |
Set cardinality |
| Command | Description |
|---|---|
zigkv server [port] |
Start server (default: 6379) |
zigkv ping |
Test if server is running |
./bench.sh [requests] [connections] |
Benchmark zigkv vs Redis |
┌─────────────────────────────────────────────────────────────┐
│ zigkv Server │
├─────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ RESP Parser │ │ Event Loop │ │ Command Router │ │
│ │ (Protocol) │ │ (epoll/kqueue)│ │ (Dispatcher) │ │
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
│ │ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Store │ │
│ │ ┌────────────────┐ ┌──────────────────────────┐ │ │
│ │ │ StringHashMap │ │ Lists / Hashes / Sets │ │ │
│ │ │ (Key → Value) │ │ (lazy TTL on access) │ │ │
│ │ └────────────────┘ └──────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
- String entries:
std.StringHashMap(Entry)— O(1) average access - Entry:
{ data: []u8, expiry: ?i64 }—dataowns the key and value in one block (key atdata[0..k], value after), so each string is a single heap allocation per key - Lists / Hashes / Sets:
StringHashMap-backed per key - TTL: lazy, checked on access (no background sweeper)
- Allocation:
GeneralPurposeAllocatorin debug builds,std.heap.smp_allocatorin release
zig build test# Start server in background
zigkv server &
# Test with redis-cli
redis-cli PING
# → PONG
redis-cli SET hello world
# → OK
redis-cli GET hello
# → world
# Benchmark
./bench.sh 1000000 50
# Cleanup
pkill zigkvzig build -Doptimize=ReleaseFast
./bench.sh 1000000 50 # requests, connections -- writes both raw outputs +
# a markdown comparison table to bench-results/zigkv/
├── build.zig # Zig build configuration
├── build.zig.zon # Package manifest
├── README.md # This file
└── src/
├── main.zig # Entry point and CLI dispatch
├── resp.zig # RESP2 protocol parser/writer
├── store.zig # In-memory storage engine
├── commands.zig # Command execution logic
├── server.zig # TCP server and event loop
└── (repo-root bench.sh drives redis-benchmark comparisons)
- Pure Zig — No C dependencies, no external libraries
- Single Binary — Statically linked, self-contained
- Zero Alloc on Read Path — Minimal GC pressure
- Simple is Fast — Optimized for common operations
- Redis Compatible — Works with existing Redis clients
- Basic SET/GET/DEL
- TTL/EXPIRE support
- INCR/DECR
- RESP2 protocol
- String / List / Hash / Set types
- Benchmark comparison (
bench.shvs real Redis) - Snapshot save/load (JSON)
- Batch mode (NDJSON)
- AOF persistence
- Pub/Sub
- Lua scripting
- Cluster mode
Contributions welcome! This is a pure Zig project — no C, no external dependencies.
# Clone and build
git clone https://github.com/sudo-su-coffee/zigkv
cd zigkv
zig build -Doptimize=ReleaseFast
# Run tests
zig build test
# Format code
zig fmt src/MIT License — See LICENSE file for details.
- Inspired by Redis and the simplicity of single-binary tools
- Part of the sudo-su-coffee toolchain
Built with ❤️ in pure Zig. No packages. No nonsense. Just fast code.
Start your server in release mode for maximum speed: Bash
zig build -Doptimize=ReleaseFast ./zig-out/bin/zigkv server 6379
In another terminal, run the official Redis benchmark tool: Bash
redis-benchmark -h 127.0.0.1 -p 6379 -t set,get -n 100000 -q