Five machines.
One log.
No disagreement.
An implementation of the Raft consensus algorithm — no consensus library, the algorithm is the project — behind a linearizable key-value store, verified against injected partitions, crashes and clock skew.
A leader appends entries; once a majority holds an entry, it commits. Every node ends in the same state.
The algorithm is a pure function.
The Raft core reads no clock, opens no socket, and writes no file. It takes a message and the current time and returns new state plus the messages to send. All the messy parts — the network, the disk, the wall clock — live in a thin impure driver around it. A test parses every module in the core and fails if it imports anything that does I/O.
The core
A deterministic state machine over messages. Because it has no I/O, an entire five-node cluster is five objects in one process — so the test suite runs thousands of randomized failure scenarios in seconds, and a seed reproduces any failure exactly.
- Leader election with PreVote
- Log replication + the Figure 8 commit rule
- Snapshotting & log compaction
- Single-server membership changes
- ReadIndex linearizable reads
The driver
Owns the clock, the disk, and the network, in that strict order: persist to a fsync'd write-ahead log before any message is sent. Swap the transport and the same core runs in a seeded simulator or a real gRPC cluster.
- Write-ahead log + fsync + torn-tail recovery
- gRPC transport, 3-node docker cluster
- Client sessions for exactly-once commands
- All four safety properties asserted every tick
The linearizability checker earned its keep.
A Jepsen-style checker records every client operation with real timestamps and searches for a valid sequential ordering (Wing & Gong, memoized, decomposed per key). Mandatory negative controls — deliberately broken variants — confirm it actually rejects bad histories. It caught two real bugs that the entire conformance suite missed, both outside the consensus core:
The wrong answer to the right client
Pending client requests were keyed by log index. A deposed leader could hand a client a different command's result after its slot was overwritten. Fixed: match the applied entry's (client_id, seq) before resolving, and fail pending requests on loss of leadership.
A fresh leader reading the past
A newly elected leader holds every committed entry but hasn't advanced its commit index over them until its own-term no-op commits. A ReadIndex read served before that could miss a committed write. Fixed: read at max(commitIndex, term-start index) and wait for the state machine to catch up.
In both cases the consensus core was correct. The bugs lived in read and result handling — and only checking real histories against real-time order surfaced them.
Every claim has a command behind it.
Nothing here is asserted. Each number is reproducible from the repository.
Ten phases, one at a time.
Types and log → persistence → elections → replication → KV & sessions → gRPC → snapshotting → membership → linearizable reads → the checker → the benchmark.