TL;DR
A developer built a small Redis-compatible key/value server in Zig that allocates all required memory at startup and reuses it for the process lifetime. The design uses fixed pools for connections and buffers, io_uring for async I/O bookkeeping, and a bump-style FixedBufferAllocator for parsing to achieve zero-copy command handling.
What happened
The author is developing a Redis-compatible key/value server called kv in Zig and adopted a development style that statically allocates all memory during initialization. Inspired by the TigerBeetle "TigerStyle" approach, the server reserves pools for Connection objects and for receive/send buffers up front, avoiding dynamic allocation or freeing after startup. Connection state holds an io_uring completion, a socket handle, and pointers to per-connection byte buffers. Pools are created with fixed capacities (via std.heap.MemoryPoolExtra and custom ByteArrayPool with a free list) so runtime requests simply pull and return slots; requests are rejected if no slot is available. Command parsing follows the RESP format and uses slices into the request buffer for zero-copy parsing; temporary bookkeeping and copied response items are allocated from a FixedBufferAllocator (a bump allocator) that is reset after each single-threaded request. Configuration values determine pool sizes and buffer lengths via a computed allocation() method.
Why it matters
- Allocating all memory at startup reduces runtime allocation jitter and potential use-after-free bugs.
- Fixed-size pools create predictable resource limits and simplify failure handling when capacity is exhausted.
- Zero-copy parsing and a bump allocator minimize per-request overhead, making parsing and response assembly cheap.
- Explicit, upfront sizing forces designers to reason about workload and limits, which can lead to simpler and more maintainable systems.
Key facts
- Project: a Redis-compatible key/value server named kv, implemented in Zig.
- Design principle: all memory requested and allocated from the OS at startup; no dynamic allocation after initialization.
- Connections track a completion (for io_uring), client socket, and pointers to receive/send ByteArray buffers.
- Three pools are created at init: Connection structs pool, receive buffer pool, and send buffer pool.
- Connection pools use std.heap.MemoryPoolExtra with initPreheated; ByteArrayPool is custom and uses a free list.
- If a connection slot is unavailable at runtime, the incoming request is rejected rather than allocating more memory.
- Command parsing targets the Redis RESP format and prefers zero-copy: parsed items are slices into the request buffer.
- A std.heap.FixedBufferAllocator (bump allocator) is used for per-request bookkeeping and copied items; it is reset after each request.
- The server described is single-threaded and processes one request at a time, allowing reuse of the FixedBufferAllocator.
- Configuration values feed a computed allocation() method that determines per-connection buffer sizes (e.g., connection_recv_size and connection_send_size).
What to watch next
- Detailed implementation and trade-offs for the key/value storage layer — not confirmed in the source.
- Behavior and performance characteristics under high connection or workload pressure (how often pools are exhausted) — not confirmed in the source.
- Potential extensions to multi-threaded handling or concurrency models beyond the single-threaded design — not confirmed in the source.
Quick glossary
- Static allocation: Allocating needed memory up front during program initialization and reusing it for the process lifetime instead of allocating and freeing at runtime.
- io_uring: A Linux kernel interface for asynchronous I/O that uses submission and completion contexts to perform non-blocking operations.
- Bump allocator (FixedBufferAllocator): A simple allocator that hands out memory linearly from a buffer and resets the allocation pointer to free all allocations at once.
- Zero-copy parsing: Processing input by creating references (slices) into the original buffer instead of copying the data into new memory regions.
- Memory pool: A preallocated set of fixed-size objects or buffers that code can borrow and return, avoiding per-object heap allocation.
Reader FAQ
Is kv fully compatible with Redis?
The project implements a small subset of Redis commands to be compatible with RESP, not full Redis compatibility.
Does the server perform dynamic memory allocation during normal operation?
No — the design allocates memory at startup and avoids dynamic alloc/free after initialization.
How are buffer sizes and pool limits determined?
A Config struct computes allocation() after options are set; values like connection_recv_size and connection_send_size derive from those options.
Is the server multi-threaded?
The described server is single-threaded and handles one request at a time.
Where is the code repository referenced?
The source references a repository at https://github.com/nickmonad/kvt.
Over the past few months I've been chipping away at a small Redis-compatible key/value server called kv. The goal is to have something (mostly) production-ready, while implementing only a small…
Sources
- Static Allocation with Zig
- I'm building a Redis Clone in Zig: A Deep Dive into Pub …
- Building a Redis Clone in Zig Newsletter
- Building a Redis Clone in Zig—Part 1
Related posts
- How to adjust what online platforms show you: tuning algorithms
- Experimental Carnegie Mellon camera lens can focus every object at once
- CIA ‘Star Gate’ Project: 1993 Overview Document (PDF) — CIA Reading Room