all projects

projects / 02

basalt

persistent in-memory KV store

go · 2026 · systems
aofobsolete → dropped on compactioncrc32opklenvlenkeyvaluememshard 00shard 01· · ·shard 31
fig.02 — append-only log → record anatomy → sharded memtable

A persistent in-memory key-value store in Go. State lives in a 32-way sharded map (fnv32, one RWMutex per shard) and every mutation is appended to an AOF before the map changes. On startup the log is read through mmap and replayed; torn writes at the tail are truncated and CRC mismatches are reported as corruption.

concurrent get
~60ns
concurrent get
writes / sec
~478k
writes / sec
allocs on writes
0 B/op
allocs on writes

how it works

  1. 01

    Write

    Set and Delete hash the key with fnv32 to one of 32 shards, take that shard's lock, append a record to the AOF and only then update the map. If the append fails, the map is left unchanged.

  2. 02

    Encode

    Each record is CRC32, op, varint key length, varint value length, key and value. Buffers come from a sync.Pool and the varints live on the stack, so the write path allocates nothing.

  3. 03

    Read

    Get takes a read lock on one shard only, so reads on different shards never contend.

  4. 04

    Recover

    On startup the AOF is memory-mapped and replayed. A record cut short at the end of the file is treated as a torn write and truncated; a CRC mismatch on a complete record is reported as corruption.

  5. 05

    Compact

    Every five minutes the store is snapshotted into a new log while writers keep going, with their changes buffered on the side. Writes pause only for the final step: replay the buffer, fsync, and rename the new log over the old one.

design decisions

Append-only log over snapshots

Appending is sequential and cheap, and replaying the log rebuilds the exact state. Compaction keeps the file from growing forever.

32 shards instead of one lock

A single RWMutex serialises every writer. Sharding by key hash lets unrelated keys proceed in parallel; writes to the same shard are still ordered, so log order matches map order.

mmap for recovery

Recovery walks the whole file once; mapping it avoids issuing a read call per record.

highlights

  • Each record is CRC32 · op · varint lengths · key · value, so bit-rot is detected instead of replayed
  • Zero-allocation write path: stack varints and sync.Pool buffers keep the GC out of it
  • Compaction snapshots while writers keep going, then pauses writes only for the final log swap

known limitations

  • The AOF is fsynced once per second, so a power loss can drop up to about a second of acknowledged writes.
  • A corrupted length field mid-file looks like a torn write, so everything after it is truncated.
  • The whole dataset must fit in RAM, and compaction briefly needs disk for a second copy.
  • Local CLI only, with no network server yet; runs on Linux and macOS (use WSL on Windows).

stack

  • go
  • aof
  • mmap
  • crc32
  • sync.Pool

Building something on the backend? Say hi.

© 2026 Avichal Pandey

ping me·

try: curl -L avichal.me