Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

zigkv

In-memory key-value store written in pure Zig — Redis-compatible, zero dependencies, single binary.

🚀 Features

  • 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

📦 Installation

From Source

git clone https://github.com/sudo-su-coffee/zigkv
cd zigkv
zig build -Doptimize=ReleaseFast
# Binary at ./zig-out/bin/zigkv

Pre-built Binary

# 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

🎯 Usage

Server Mode

# Start server on default port 6379
zigkv server

# Custom port
zigkv server 6380

Redis Protocol (RESP2)

# 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
# → 58

Batch Mode & Persistence (not yet implemented)

Batch-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.

Benchmark

zig build -Doptimize=ReleaseFast
./bench.sh 1000000 50          # requests, connections

bench.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.)

📊 Benchmarks

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

Test hardware (this box)

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)

Latest measured run (1,000,000 requests, 50 connections)

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.

🔌 Embedding as a library

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.

🔧 Commands

Core Commands

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

List Commands

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)

Hash Commands

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

Set Commands

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

CLI Commands

Command Description
zigkv server [port] Start server (default: 6379)
zigkv ping Test if server is running
./bench.sh [requests] [connections] Benchmark zigkv vs Redis

🏗️ Architecture

┌─────────────────────────────────────────────────────────────┐
│                      zigkv Server                          │
├─────────────────────────────────────────────────────────────┤
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐ │
│  │   RESP Parser │  │  Event Loop  │  │  Command Router  │ │
│  │  (Protocol)   │  │ (epoll/kqueue)│  │  (Dispatcher)   │ │
│  └──────────────┘  └──────────────┘  └──────────────────┘ │
│                            │                               │
│  ┌──────────────────────────────────────────────────────┐  │
│  │                     Store                           │  │
│  │  ┌────────────────┐  ┌──────────────────────────┐ │  │
│  │  │ StringHashMap  │  │  Lists / Hashes / Sets  │ │  │
│  │  │  (Key → Value) │  │  (lazy TTL on access)   │ │  │
│  │  └────────────────┘  └──────────────────────────┘ │  │
│  └──────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

Memory Layout

  • String entries: std.StringHashMap(Entry) — O(1) average access
  • Entry: { data: []u8, expiry: ?i64 } — data owns the key and value in one block (key at data[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: GeneralPurposeAllocator in debug builds, std.heap.smp_allocator in release

🧪 Testing

Unit Tests

zig build test

Manual Testing

# 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 zigkv

Performance Testing

zig build -Doptimize=ReleaseFast
./bench.sh 1000000 50   # requests, connections -- writes both raw outputs +
                        # a markdown comparison table to bench-results/

📁 Project Structure

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)

🔒 Design Principles

  1. Pure Zig — No C dependencies, no external libraries
  2. Single Binary — Statically linked, self-contained
  3. Zero Alloc on Read Path — Minimal GC pressure
  4. Simple is Fast — Optimized for common operations
  5. Redis Compatible — Works with existing Redis clients

🚧 Roadmap

  • Basic SET/GET/DEL
  • TTL/EXPIRE support
  • INCR/DECR
  • RESP2 protocol
  • String / List / Hash / Set types
  • Benchmark comparison (bench.sh vs real Redis)
  • Snapshot save/load (JSON)
  • Batch mode (NDJSON)
  • AOF persistence
  • Pub/Sub
  • Lua scripting
  • Cluster mode

🤝 Contributing

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/

📄 License

MIT License — See LICENSE file for details.

⭐ Acknowledgments

  • 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

About

In-memory key-value store written in pure Zig — Redis-compatible, zero dependencies, single binar

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages