Engineering · 2025
Raft Bare Bones
A teaching-first implementation of the Raft consensus algorithm in Go, with readable election and replication logic, an HTTP transport, and a small replicated key/value application.
Why I built it
Raft is often introduced as the understandable consensus algorithm. The paper is unusually clear, but reading an algorithm and implementing its state transitions are different kinds of understanding.
I built Raft Bare Bones to test whether I understood the algorithm well enough to make it work—and then explain it through code. The goal was not to create another production-grade Raft library. It was to build one small enough to read in a sitting, trace end to end, and modify without first navigating layers of optimisation and defensive machinery.
That choice shaped the project: clarity over throughput, explicit interfaces over clever abstractions, and enough real I/O to observe consensus rather than only simulate it in tests.
What I built
Raft Bare Bones is a minimal Go implementation of leader election and replicated logs. It includes the consensus core, JSON-over-HTTP transport, an in-memory log, and a key/value application that uses the replicated log as its source of truth.
The repository is split by responsibility:
corecontains elections, replication, timers, node state, and the interfaces for storage, transport, and application state;transport/httpserverandtransport/httpclientexposeRequestVoteandAppendEntriesas ordinary HTTP requests;apps/kvimplements a small deterministic finite-state machine and its HTTP handlers;cmd/rbbwires a node, transport, storage, and application into a runnable process;loggingkeeps role, term, election, and commit transitions visible through structuredslogoutput;scriptscontains the three-node local-cluster helper.
Keeping networking and application behaviour outside the consensus state machine was an important design constraint. The core package can reason about peers, terms, votes, log entries, and commits without knowing whether messages travel over HTTP or what an applied command means to the application.
Following a write through the cluster
Applications submit commands through node.Propose. In the example key/value
store, an HTTP request is encoded into a log entry and proposed to the local
node.
Only the leader can accept that proposal. A follower returns not leader, which
the HTTP layer exposes as a 409, so a client can redirect or retry against the
current leader.
The leader appends the command to its log and replicates it to peers through
AppendEntries. Once a majority has accepted the entry, the commit index can
advance and the command is applied to the finite-state machine. Each node applies
the same committed commands in the same order, producing the same key/value
state.
The implementation keeps this path deliberately visible. The transport uses JSON, and meaningful transitions are logged, so election starts, role changes, replication, and commit-index movement can be followed without specialised observability tooling.
Watching consensus happen
The included script starts three processes on ports 7001–7003:
make run-3node
After a leader is elected, writes can be sent to that node and read from the others after replication:
curl -X POST http://127.0.0.1:7001/kv/consensus -d 'raft'
curl http://127.0.0.1:7002/kv/consensus
Killing the leader makes the mechanics concrete: the remaining nodes time out, start an election, choose a new leader, and continue accepting writes. Restarting the old process lets it catch up from the replicated log.
The demo was useful because it turned abstract properties—majority agreement, leader authority, ordered application, and failover—into behaviour I could inspect one request and log line at a time.
Extending it with another application
The key/value store is only an example. A different application implements
core.FSM, encodes its commands into core.LogEntry.Data, and applies committed
entries deterministically in Apply.
After attaching that FSM to a node, the application only needs thin I/O glue:
domain-specific handlers prepare commands for node.Propose, while transport
errors such as ErrNotLeader and ErrNoTransport become useful client
responses.
The repository includes a step-by-step replicated-counter guide to demonstrate this boundary with something smaller than the key/value service.
Deliberate limitations
This is a learning scaffold, not a production consensus library. Logs currently live in memory, cluster membership is static, and the implementation does not include snapshots, membership changes, read-index support, or replication batching and pipelining.
Those omissions are intentional. Production Raft implementations need extensive validation, durable storage semantics, recovery handling, performance work, and many edge-case guards. Adding all of that would make this project less useful for its original purpose: seeing the core algorithm clearly.
The architecture guide explains the package boundaries and write path in more detail, while the roadmap keeps the missing capabilities explicit.