Single-node, in-memory key/value store over TCP, written in x86-64 assembly (NASM). No libc, raw Linux syscalls only. Statically linked, ~13 KB binary, ~3 MB RSS.
make
./kvstore [port] # default 4242
Requires Linux x86-64, nasm, ld.
$ printf 'SET user:1 hello\nGET user:1\nGET nope\n' | nc localhost 4242
OK
hello
(nil)
Tests (need python3):
./kvstore 4747 &
python3 smoke_test.py 4747 # protocol + storage regression suite
python3 killer.py 4747 # concurrency: mid-line client + 200 simultaneous clients
Not required for anything below — kept as a convenient way to ship or try the server.
The binary is fully static, so the runtime image is FROM scratch: a single ~13 KB file.
docker build -t kvstore .
docker run --rm -p 4242:4242 kvstore # override the port: ... kvstore 5000
On ARM64 hosts (Apple Silicon), force the target arch; Docker Desktop runs it through Rosetta or QEMU:
docker build --platform linux/amd64 -t kvstore .
docker run --rm --platform linux/amd64 -p 4242:4242 kvstore
The binary targets Linux x86-64, so on a Mac it builds and runs inside a lightweight Linux VM. With OrbStack:
orb create -a amd64 ubuntu kv # once: amd64 Ubuntu machine (Rosetta-backed)
orb -m kv # shell in; the macOS home is mounted at the same path
sudo apt update && sudo apt install -y nasm make binutils netcat-openbsd
cd ~/path/to/kvstore
make && ./kvstore 4242
Edit on the macOS side, make in the VM — same files, no syncing.
From macOS the server is reachable at kv.orb.local:4242. Inside the VM, poke it with nc -N (-N makes netcat exit once its input ends; without it nc lingers — harmless to
the server, annoying for you).
Any Linux VM (Lima, UTM, WSL2 on Windows) works the same way.
Plain text, one command per \n-terminated line (trailing \r stripped).
Keys cannot contain spaces or newlines; values may contain spaces.
| Command | Reply |
|---|---|
SET <key> <value> |
OK |
GET <key> |
<value> | (nil) |
DEL <key> |
OK | (nil) |
PING |
PONG |
QUIT |
BYE, then closes |
Errors: ERR usage: ..., ERR unknown command, ERR store full,
ERR arena exhausted, ERR line too long (line > 64 KB, connection closed).
- FNV-1a 64-bit hashing, open addressing with linear probing, tombstones on delete
- 65,536 slots of 32 bytes (2 MB table), insert cap at 49,152 occupied slots
- 256 MB anonymous
mmaparena, bump allocation, key+value stored contiguously epollevent loop, non-blocking sockets; per-connection 64 KB read buffer (fd-indexed state table), partial lines carried across reads- Replies via
sendto+MSG_NOSIGNAL: a dead peer cannot SIGPIPE the process - Measured ~47k ops/s pipelined over loopback (round-trip through a Python client)
- TCP server:
socket/bind/listen/accept4,SO_REUSEADDR, port from argv - Commands:
SET,GET,DEL,PING,QUIT - FNV-1a hash table with linear probing and tombstones
- Tombstone reuse on insert (delete/insert cycles don't degrade the table)
-
mmapbump-allocator arena, single allocation perSET - Buffered reads, pipelining, partial lines across
readcalls - CRLF tolerance, values with spaces, empty values
- Oversized line rejection (error reply, then close)
- Capacity guards:
ERR store full,ERR arena exhausted - Partial-write-safe send loop, SIGPIPE-proof (
MSG_NOSIGNAL) - Smoke test suite: 20k keys, 30 KB values, tombstone cycles, pipelining, cross-connection reads
- Multi-stage Dockerfile,
FROM scratchruntime (binary verified self-contained via chroot into an empty root) -
epollevent loop: non-blocking sockets, per-connection read state, 200+ simultaneous clients
- Write buffering for slow readers: queue replies per connection, arm
EPOLLOUT(currently a slow reader whose socket buffer fills up is dropped) - RESP protocol compatibility (talk to
redis-clidirectly) - Persistence: append-only file, replay on boot, periodic
fsync - Key expiry:
EXPIRE/TTL, lazy eviction on access - More commands:
EXISTS,INCR,KEYS/SCAN,STATS - Arena reclamation: free old blobs on overwrite (free list or compaction)
- Table resize + rehash instead of the fixed 64k slots
- Backward-shift deletion (drop tombstones entirely)
- Binary-safe values: length-prefixed protocol, allow
\nin payloads - Graceful shutdown on
SIGINT/SIGTERM - Flags: bind address, table size, arena size
- Linux x86-64 only
- A slow reader whose socket buffer fills up gets disconnected (write buffering not yet implemented — first item on the roadmap)
- Up to 1024 simultaneous connections (fd-indexed state table)
- No persistence: data dies with the process
- Command line must fit in 64 KB
- Overwriting a key leaks the old blob in the arena (bump allocator, by design for now)