raft-kv — Raft from Scratch in Rust

Ruststd-only

A replicated key-value store built on my own implementation of the Raft consensus algorithm, written to be defensible line-by-line rather than big. The whole workspace is std-only — no tokio, no serde, no rand. One small PRNG, one hand-rolled wire codec, one event loop; everything interesting is the consensus logic and the tests that hammer it.

Sans-I/O core

raft-core contains no sockets, no clocks, no threads. A node is a deterministic state machine: the host calls tick() on its own schedule, feeds messages to handle(), submits commands with propose(), and executes the effects the node emits:

for effect in node.take_effects() {
    // PersistHardState / PersistLog -> make durable *before* later sends
    // Send { to, message }          -> put on the wire, best-effort
    // Apply { index, data }         -> feed the state machine, in order
}

The production server honors that contract with real files and TCP. The test harness honors it with an in-memory “disk” and a simulated network. Same core, byte for byte — that inversion is the whole point.

Deterministic simulation testing

Clusters run on a virtual clock with seeded randomness: message delay, loss, duplication, reordering, partitions, crashes, and restarts — where restarted nodes are rebuilt from the mirrored persistence effects, so the durability contract is tested rather than assumed. After every step the harness asserts Raft’s safety properties:

  • Election Safety — at most one leader per term, ever
  • Log Matching — same (index, term) means same command, across all disks
  • State Machine Safety — a slot that applies once applies identically everywhere
  • Term monotonicity — no node’s term ever decreases

Scenarios include a 5-node cluster under 20% message loss with duplication and node bounces across 25 seeds, a minority-partitioned leader whose proposals must never commit anywhere, and a full-cluster power cut. Any failure reproduces exactly from its seed. The whole suite runs in about a second.

The server

One event-loop thread owns the Raft node, the KV map, and durable storage; plain blocking threads feed it through a channel. All operations — reads included — go through the log, so reads are linearizable at the cost of a round of consensus. Storage is an atomically-rewritten hard-state file plus an append-only framed log that survives torn tails from a crash mid-append.

Verified live: 3-node cluster, put/get/del through any node (followers redirect to the leader), kill the leader mid-session — a new leader takes over with nothing lost, and the restarted node catches up from its own disk.

Deliberately not there yet

Snapshots/log compaction, ReadIndex reads, pre-vote, and membership changes — in that order. Scoping those out of v1 kept the correctness surface small enough to actually test to death.