Systems software · Source published
RustKV
A persistent key-value server in Rust with a bounded binary protocol and crash-conscious storage.
A single-node TCP key-value service with concurrent clients, synchronized shared state, TTLs, a CRC32 append-only log, restart replay, compaction, and structured metrics.
- Role
- Systems design, protocol and persistence engineering, concurrency, testing, benchmarking, and CI.
- Year
- 2026
- Status
- Source published
- Stack
- Rust · TCP sockets · Arc / RwLock / Mutex · CRC32 · tracing · Criterion · GitHub Actions
Overview
RustKV is a compact persistent network key-value server: one process, one node, a deterministic binary protocol, concurrent TCP clients, synchronized shared state, an append-only log, TTLs, compaction, metrics, tests, and reproducible benchmarks.
It is a systems-programming project, not a Redis replacement. Its scope is deliberately small so that ownership, locking, durability ordering, and recovery behavior stay visible.
Problem
A key-value store looks simple until it has to survive concurrent clients, malformed network input, and a crash in the middle of a write. Each of those failure modes needs an explicit, testable answer rather than an assumption.
Solution
RustKV frames every request in a versioned 1 MiB length-prefixed protocol, serves each connection on a capped OS thread, shares state through Arc with RwLock and Mutex under a documented lock order, and writes every effective mutation to a CRC32-protected log with sync_data before memory changes and before success is returned.
Feature evidence
Bounded binary protocol
Versioned length-prefixed frames with deterministic handling of malformed, oversized, and partial input.
Concurrent connections
Persistent TCP connections served by capped thread-per-connection admission with graceful shutdown.
Durable append-only log
CRC32 records, ordered restart replay, truncated-tail repair, and fail-closed handling of interior corruption.
TTLs and compaction
Absolute-expiry persistence that does not resurrect expired overwrites, plus crash-conscious log compaction.
System layers
Each connection thread decodes frames and hands commands to an executor that operates on a shared Arc<Database>. The store sits behind an RwLock, the append log behind a Mutex, and runtime counters are atomics exposed through STATS.
- 01
CLI / client
rustkv-cli and the client library speak framed TCP.
- 02
Connection thread
Capped admission, persistent sockets, and shutdown checks.
- 03
Codec + executor
Frame validation and command execution against shared state.
- 04
RwLock store
Binary-safe keys and values with TTL visibility.
- 05
Append-only log
CRC32 records, sync_data ordering, replay, and compaction.
Where the engineering concentrates.
Recovering from a torn write
A final incomplete record is discarded at the last known-good boundary, while complete corrupt records stop startup instead of being silently skipped.
Compaction across rename states
Log replacement uses replacement and backup states so an interrupted compaction can be recovered on Windows as well as Unix.
Choices and tradeoffs.
Threads instead of async
One capped OS thread per connection keeps ownership, blocking I/O, and lock behavior explicit.
Tradeoff: Simpler to reason about, but connection count is bounded by threads rather than an event loop.
Sync before acknowledge
A mutation reaches disk before memory changes and before the client sees success.
Tradeoff: Serializes mutations and pauses readers during disk sync in exchange for clear durability ordering.
Fail closed on corruption
Interior checksum failures stop startup rather than serving partially recovered data.
No throughput claims without context
Benchmark numbers are only reported alongside the machine, mode, build profile, and workload that produced them.
Security, accessibility, and testing.
Security
- Bounded 1 MiB frames reject oversized input
- Deterministic handling of malformed and partial frames
- Binds to 127.0.0.1 by default
- Capped connection admission
Accessibility
- Command-line interface with plain-text output
- Documented protocol, persistence, and concurrency behavior
Testing
- Unit tests for store, protocol, and persistence behavior
- Real-socket integration tests with concurrent clients
- Malformed-frame, failure, and graceful-shutdown cases
- Restart and persistence-recovery tests against real temporary files
- Format, Clippy, test, release build, and benchmark compilation on Windows and Linux CI
Current boundaries
The current product has a deliberate scope. These boundaries define what it does not claim.
- Single process and single node
- No authentication, TLS, replication, clustering, or transactions
- One OS thread per active connection
- Synchronous persistence serializes mutations
- Expired entries can occupy memory until a mutation, recovery, or compaction
- Does not claim ACID semantics
The next credible steps.
- 01Add parser fuzzing and property-based tests
- 02Add configurable group commit or a dedicated persistence writer
- 03Add background active expiration
What the implementation clarified.
- 01
Durability is an ordering problem: the log write and sync must happen before memory changes and before the client is told the write succeeded.
- 02
Treating a torn final record differently from interior corruption lets recovery be forgiving where it is safe and strict where it is not.
- 03
A documented lock order turns shared-state concurrency from an implicit habit into something reviewable and testable.