Raft Consensus Algorithm

Imagine five servers holding replicas of a bank's account database. The machine acting as leader crashes mid-transaction. Some replicas received the last update, others did not. Without a disciplined protocol, the cluster can silently diverge. One replica believes a withdrawal happened, another does not. As a result, money vanishes or duplicates. This is the fundamental problem of distributed consensus. It means getting unreliable machines to agree on one ordered sequence of operations. This must hold despite crashes, message delays, and network partitions. Raft was designed explicitly to solve this problem. It aims to be easier to understand and implement than Paxos Consensus Protocol. Paxos is notoriously difficult to reason about and implement correctly. Raft decomposes consensus into three largely independent subproblems. These are leader election, log replication, and safety. It enforces a strong invariant called leader completeness. This makes the whole system's correctness arguable in a handful of lemmas. It achieves the same fault-tolerance guarantees as Paxos. A cluster of `2f+1` servers tolerates `f` crash failures. It keeps making progress as long as a majority is alive and can communicate. Raft matters because it underlies production systems that people depend on every day. etcd (Kubernetes' configuration store), Consul, CockroachDB, TiKV, and countless custom replicated state machines all use Raft or a close variant. This keeps replicas consistent. Understanding Raft is the practical gateway to understanding modern distributed databases. It shows how they achieve strong consistency without a single point of failure. This article builds Raft from the ground up. It covers the server state machine (follower, candidate, leader). It covers the RPCs that drive elections and replication. It covers the term mechanism that provides logical time. It covers the safety proof that guarantees committed entries are never lost. We trace a concrete five-server example through a leader crash and a network partition. We catalog the classic implementation pitfalls. We connect Raft to the broader landscape of Replication and Consistency Models.

Key takeaways
  • Raft replicates a log across servers; agreement on the log order equals agreement on the sequence of operations to apply to a state machine.
  • Time is divided into terms, each with at most one leader, elected via randomized-timeout elections that avoid split votes with high probability.
  • Only the leader accepts client requests, appends entries to its log, and replicates them to followers via `AppendEntries` RPCs (also the heartbeat).
  • An entry is committed once it is stored on a majority of servers; committed entries are guaranteed durable across any future leader changes.
  • The election restriction — a candidate must have a log at least as up-to-date as a majority — is the single mechanism that makes safety provable.
  • Raft trades a small amount of theoretical generality (it assumes crash-stop, not Byzantine, failures) for a dramatic gain in implementability and pedagogical clarity.
The core insight

Raft achieves consensus by electing a single leader per term who unilaterally orders client operations into a replicated log, and by restricting elections so that only servers with the most up-to-date log can become leader — guaranteeing that every committed entry survives all future leadership changes.

The idea

A replicated state machine is a technique for making a service fault-tolerant by running identical copies on multiple servers and ensuring each copy processes the exact same sequence of commands in the exact same order. If all replicas start in the same state and apply the same deterministic operations in the same order, they remain identical forever. The hard part is not applying operations — it is agreeing on their order in the presence of crashes and unreliable networks. That agreement problem is consensus. Raft solves consensus by imposing structure that Paxos deliberately avoids. Every server is at all times in exactly one of three roles: follower, candidate, or leader. Time is partitioned into consecutive, monotonically increasing terms, each identified by an integer. At most one server can be leader in a given term — this is enforced by a majority-vote election — and a term may also have no leader if an election fails (split vote). The leader is the sole point of contact for clients; it appends new commands to its local log and replicates that log to followers. Followers are purely passive: they accept the leader's log entries and respond to RPCs, but never initiate anything except starting an election when they suspect the leader has failed. The key terminology: a log entry is a tuple `(term, index, command)`. An entry is committed once the leader has confirmed it is stored on a majority of servers in the cluster; once committed, Raft guarantees the entry will never be lost or reordered, no matter how many subsequent leader crashes occur. Log matching is the property that if two logs contain an entry with the same index and term, all preceding entries in both logs are identical — this lets a leader replicate incrementally without ever needing an expensive log-reconciliation protocol. Election safety guarantees at most one leader per term, and leader completeness guarantees that any server elected leader in a later term already holds all entries committed in earlier terms. These properties compose into the overall safety guarantee: no two servers ever apply different commands at the same log index.

How it works

Raft's mechanism has three interlocking parts.

1. Leader election. Every follower runs an election timer with a randomized timeout (typically 150–300 ms). If it receives no `AppendEntries` heartbeat from a leader before the timer fires, it assumes the leader is dead, increments its `currentTerm`, transitions to candidate, votes for itself, and sends `RequestVote` RPCs to all other servers in parallel. A server grants its vote if (a) it has not already voted in this term, and (b) the candidate's log is at least as up-to-date as its own (the election restriction, detailed below). A candidate becomes leader upon receiving votes from a majority. If it instead receives an `AppendEntries` from a legitimate leader with an equal or higher term, it steps down to follower. If neither happens before its own timeout expires, it starts a new election with an incremented term — randomization keeps the probability of repeated split votes low.

2. Log replication. Once elected, the leader accepts commands from clients, appends each as a new entry to its own log, and sends `AppendEntries` RPCs (in parallel) to replicate the entry to every follower. Each RPC includes the index and term of the entry immediately preceding the new ones (`prevLogIndex`, `prevLogTerm`); a follower rejects the RPC unless its log already contains a matching entry at that position, which enforces the Log Matching Property inductively. When the leader learns that an entry is stored on a majority of servers, it marks that entry (and all preceding ones) as committed and applies it to its own state machine; it communicates the new commit index to followers via subsequent heartbeats, and followers then apply the same entries to their own state machines.

3. Safety enforcement. Two rules guarantee that a newly elected leader cannot silently erase committed history. First, the election restriction: a `RequestVote` is rejected if the candidate's last log entry is less up-to-date than the voter's — "more up-to-date" is defined by comparing `(lastLogTerm, lastLogIndex)` lexicographically. Because a committed entry lives on a majority, and any candidate needs a majority of votes, at least one voter must have seen every committed entry, so the candidate is forced to already have it (or something newer) before it can win. Second, Raft never commits an entry from a previous term merely because it is on a majority of servers — a leader only commits directly by counting replicas of its own current-term entries; entries from prior terms are committed only indirectly, as a side effect of committing a later entry in the current term. This closes a subtle failure case described in the correctness section below. ```pseudocode // Persistent state on every server (survives crashes) currentTerm := 0 votedFor := null log[] := [] // 1-indexed; each entry: (term, command) // Volatile state on every server commitIndex := 0 lastApplied := 0 // Volatile state on leaders only (reinitialized after election) nextIndex[] := {peer: len(log)+1 for peer in peers} matchIndex[] := {peer: 0 for peer in peers} state := FOLLOWER // ---- Election timeout on a follower or candidate ---- on ElectionTimeout: currentTerm += 1 state := CANDIDATE votedFor := self votesReceived := {self} resetElectionTimer(randomized) for peer in peers: send RequestVoteRPC(term=currentTerm, candidateId=self, lastLogIndex=len(log), lastLogTerm=log[len(log)].term) to peer // ---- Handling RequestVote RPC (any server) ---- on receive RequestVoteRPC(term, candidateId, lastLogIndex, lastLogTerm): if term < currentTerm: reply (currentTerm, voteGranted=false); return if term > currentTerm: currentTerm := term; votedFor := null; state := FOLLOWER candidateUpToDate := (lastLogTerm > log[len(log)].term) or (lastLogTerm == log[len(log)].term and lastLogIndex >= len(log)) if (votedFor in {null, candidateId}) and candidateUpToDate: votedFor := candidateId resetElectionTimer() reply (currentTerm, voteGranted=true) else: reply (currentTerm, voteGranted=false) // ---- Candidate tallying votes ---- on receive RequestVoteReply(term, voteGranted) while state == CANDIDATE: if term > currentTerm: currentTerm := term; state := FOLLOWER; votedFor := null; return if voteGranted: votesReceived.add(sender) if |votesReceived| > |peers+1| / 2: state := LEADER for peer in peers: nextIndex[peer] := len(log) + 1 matchIndex[peer] := 0 sendHeartbeatsToAll() // empty AppendEntries, asserts authority // ---- Leader: client request ---- on receive ClientCommand(cmd) while state == LEADER: log.append((term=currentTerm, command=cmd)) replicateToAll() // ---- Leader: periodic replication / heartbeat ---- def replicateToAll(): for peer in peers: prevIndex := nextIndex[peer] - 1 prevTerm := log[prevIndex].term if prevIndex > 0 else 0 entries := log[nextIndex[peer] : ] send AppendEntriesRPC(term=currentTerm, leaderId=self, prevLogIndex=prevIndex, prevLogTerm=prevTerm, entries=entries, leaderCommit=commitIndex) to peer // ---- Follower/any server: handling AppendEntries ---- on receive AppendEntriesRPC(term, leaderId, prevLogIndex, prevLogTerm, entries, leaderCommit): if term < currentTerm: reply (currentTerm, success=false); return if term >= currentTerm: currentTerm := term; state := FOLLOWER; votedFor := null resetElectionTimer() if prevLogIndex > 0 and (len(log) < prevLogIndex or log[prevLogIndex].term != prevLogTerm): reply (currentTerm, success=false); return // log doesn't match yet // Delete conflicting entries, then append new ones for i, e in enumerate(entries): idx := prevLogIndex + i + 1 if len(log) >= idx and log[idx].term != e.term: log := log[:idx-1] // truncate conflict if len(log) < idx: log.append(e) if leaderCommit > commitIndex: commitIndex := min(leaderCommit, len(log)) reply (currentTerm, success=true) // ---- Leader: handling AppendEntries reply ---- on receive AppendEntriesReply(term, success) from peer: if term > currentTerm: currentTerm := term; state := FOLLOWER; votedFor := null; return if success: matchIndex[peer] := prevLogIndex_sent + numEntries_sent nextIndex[peer] := matchIndex[peer] + 1 // Commit if a majority (incl. self) replicate an entry from THIS term for N from len(log) downto commitIndex+1: if log[N].term == currentTerm and |{p : matchIndex[p] >= N}| + 1 > |peers+1| / 2: commitIndex := N; break else: nextIndex[peer] := max(1, nextIndex[peer] - 1) // back off, retry replicateToOne(peer) // ---- Any server: apply committed entries to state machine ---- loop forever: if commitIndex > lastApplied: lastApplied += 1 apply(log[lastApplied].command) ```

Figure
Raft server states and the replicated log across a five-node cluster
Follower Candidate Leader election timeout discovers leader / higher term wins majority vote split vote → new election higher term discovered → step down Leader (S1, term 3) 1,1 2,1 3,2 4,3 5,3 index,term — committed through index 4 (majority stored) Follower S2 (matches) 1,1 2,1 3,2 4,3 Follower S3 (stale/conflict) 1,1 2,1 3,1 entry 3 conflicts (term 1 vs 2) — leader forces truncate+overwrite

Complexity & correctness

Message complexity. Each `AppendEntries` round and each election round requires the leader/candidate to contact `n-1` peers, so both are `O(n)` messages per round, and elections need `O(n)` messages total (not `O(n²)`) since only the candidate initiates RPCs — replies go directly back to it. Steady-state replication sends one RPC batch per heartbeat interval per follower, so throughput-limiting cost is `O(n)` messages per committed batch of entries, and latency to commit is one round trip to a majority: `O(1)` round trips, independent of `n`, since RPCs fan out in parallel.

Time complexity of catching up a follower. If a follower's log has diverged from the leader by `k` entries, the naive backtrack-by-one strategy shown in the pseudocode costs `O(k)` RPC round trips in the worst case (real implementations optimize this with a "conflict term/index" hint returned by the follower, cutting it to `O(1)` amortized round trips per term of divergence — still `O(1)` in practice for typical crash-recovery scenarios).

Space. Each server stores the full log, `O(m)` entries where `m` is the number of commands ever proposed; production systems bound this with periodic log compaction / snapshotting, after which storage is `O(state size + uncommitted tail)`.

Correctness — why safety holds. The central theorem is the Leader Completeness Property: if an entry is committed in term `T`, it will be present in the logs of leaders of all terms `> T`. The proof is by contradiction and uses two facts baked into the mechanism: 1. Committed implies majority. An entry is committed only after the leader confirms a majority of servers (including itself) have replicated it (via `matchIndex` counting). 2. Election requires majority, and voter comparison is monotone. To win an election a candidate needs votes from a majority of servers; because any two majorities intersect in at least one server, at least one voter for the new leader must be among the majority that stored the committed entry. Suppose for contradiction that leader `L` of a later term `T' > T` does not have the committed entry `e` (committed in term `T`). Since `L` won an election, some voter `V` granted its vote to `L`, and `V` must have stored `e` (because `e` was committed on a majority, and every majority overlaps every other majority by the pigeonhole principle). The election restriction says `V` only votes for candidates whose log is at least as up-to-date as `V`'s own log (compared lexicographically by `(lastLogTerm, lastLogIndex)`). Since `V` has `e` and votes for `L`, `L`'s log must be at least as up-to-date as `V`'s at the time of voting, which forces `L`'s log to already contain `e` (or an entry that supersedes it) — contradicting the assumption. Hence leader completeness holds. ∎ (sketch) A subtlety patched by the "commit only current-term entries directly" rule: without it, a leader could replicate an old-term entry to a majority, then crash before committing anything in its own term, and a later leader — legitimately elected via the restriction — could still overwrite that entry, because being "at least as up to date" only guarantees the entry survives in the log, not that it was safely committed by the original leader's count. Raft closes this gap by never marking an old-term entry committed via replica counting alone; it becomes committed only transitively, once a same-term entry above it is committed. This is the one non-obvious wrinkle that a correctness argument must include — omitting it is a classic source of subtly broken Raft implementations.

Tradeoffs versus Paxos. Raft's strong leader simplifies reasoning and implementation at the cost of a slightly less general failure model: it assumes non-Byzantine (crash-stop) failures, requires stable leader-based flow for all writes (a single bottleneck unless combined with sharding), and pays an availability cost during leader elections (`O(election timeout)` unavailability window on leader crash, typically hundreds of milliseconds). Multi-Paxos achieves comparable throughput but with a much harder-to-verify safety argument and no canonical, widely agreed pseudocode for the full protocol including reconfiguration.

Worked example

Consider a five-server cluster `{S1, S2, S3, S4, S5}`. All start as followers in term 0 with empty logs.

StepEventS1S2S3S4S5
1S1's timer fires firstcandidate, term=1, votes selffollowerfollowerfollowerfollower
2S1 sends RequestVote(term=1) to all—grants (log empty, up to date)grantsgrantstimeout fired too late, still grants
3S1 has 5/5 votes ⇒ leaderleader, term 1followerfollowerfollowerfollower
4Client sends `SET x=1` to S1log=[(1,x=1)]————
5S1 replicates via AppendEntriesleaderlog=[(1,x=1)]log=[(1,x=1)]log=[(1,x=1)](RPC lost, log=[])
64/5 stored ⇒ majority ⇒ commit index=1commitIndex=1, applies x=1applies x=1 (on next heartbeat)applies x=1applies x=1still behind
7S1 crashes(dead)followerfollowerfollowerfollower (recovers)
8S3's timer fires first, term=2deadgrants (S3 log = as up to date)candidategrantsgrants (its shorter log loses tie only if less up to date — here S3 also has entry 1, so equal ⇒ grants)
9S3 wins 3/5+ ⇒ leader, term 2deadfollowerleader, term 2followerfollower
10Client sends `SET y=2` to S3dead—log=[(1,x=1),(2,y=2)]——
11S3 replicates; S5 is missing entry 1 — AppendEntries fails prevLogTerm check, S3 backs off `nextIndex[S5]`, resends from index 1deadlog updatedleaderlog updatedcatches up, log=[(1,x=1),(2,y=2)]
12Majority (S2,S3,S4 — already 3/5) confirms entry at index 2 ⇒ commitdeadapplies y=2commitIndex=2, applies y=2applies y=2eventually catches up

What this trace demonstrates:

  • The randomized timer breaks the tie for who becomes leader (step 1), avoiding a scenario where multiple candidates start simultaneously and split the vote.
  • Commitment requires a majority, not all servers — S5's lost RPC in step 5 does not block progress (step 6).
  • Leader completeness in action: S3, elected in term 2, must already hold the term-1 committed entry `x=1` to win votes from a majority that has it (step 8) — exactly the mechanism proven above.
  • Log repair (step 11) uses the `prevLogIndex`/`prevLogTerm` consistency check and backoff to bring a lagging follower like S5 up to date without any special-case "recovery protocol" — it is the same `AppendEntries` path used for normal replication.

Common pitfalls & traps

PitfallWhy it breaks Raft
Committing based on `matchIndex` counts for entries from previous termsViolates the current-term-commit rule; a later leader can then legally overwrite an entry that looked committed, losing data.
Not persisting `currentTerm`, `votedFor`, and the log to stable storage before replying to RPCsA crash-and-restart can then vote twice in the same term or forget an acknowledged entry, breaking Election Safety.
Using non-randomized or too-short election timeoutsCauses repeated split votes (multiple candidates tie every round) or premature elections that thrash a healthy leader.
Applying a log entry to the state machine before it is confirmed committedMakes an operation externally visible that could still be rolled back/overwritten after a leader change.
Allowing a stale leader (one that lost contact with the majority) to keep serving readsProduces stale/incorrect reads during a network partition; needs a leader lease or read-index confirmation.
Forgetting to truncate conflicting suffix entries on `AppendEntries`Followers can retain garbage entries from an old term that violate Log Matching, silently forking the log.

Pioneers & history

Raft was designed at Stanford as a direct response to the recognized difficulty of teaching, implementing, and formally verifying Paxos-family protocols. The design goal was explicitly pedagogical: decompose consensus into understandable pieces without sacrificing the fault-tolerance guarantees Paxos provides, and validate that understandability with a real user study comparing students learning Raft versus Paxos.

Origins · 1 minDiego Ongaro and how this was discovered
DO
Diego Ongaro
b. 1986–present · Stanford PhD student who co-designed Raft as his doctoral thesis project under John Ousterhout

Ongaro's dissertation, "Consensus: Bridging Theory and Practice," formalized Raft's safety proof using TLA+ style reasoning and presented empirical evidence (a controlled study across two universities) that students understood and could correctly explain Raft substantially better than Paxos after equivalent instruction time.

History
2013
— Diego Ongaro and John Ousterhout publish "In Search of an Understandable Consensus Algorithm," introducing Raft.
2014
— The full USENIX ATC paper appears; Ongaro's Stanford dissertation formalizes the safety proof and membership-change protocol.
2014
— etcd (backing Kubernetes and CoreOS) adopts Raft as its replication core, driving rapid industrial adoption.
2015–2017
— HashiCorp Consul, CockroachDB, and TiKV/TiDB all build production storage layers on Raft implementations.
2020
s — Raft becomes a default teaching example alongside Paxos Consensus Protocol in distributed systems courses worldwide, and Raft-based libraries (raft-rs, hashicorp/raft, etcd/raft) become standard building blocks.

Where this matters

Raft is the replication engine inside etcd, the strongly-consistent key-value store that Kubernetes uses for all cluster state — every pod schedule, service definition, and config map is committed through a Raft log before it takes effect. HashiCorp Consul and Nomad use Raft for service-discovery and scheduler state. CockroachDB and TiDB/TiKV shard data into many small Raft groups (one per data range) to get horizontal scalability while keeping each range strongly consistent — an architecture sometimes called "Multi-Raft." Kafka's KRaft mode (replacing ZooKeeper) uses a Raft-derived protocol to replicate cluster metadata. Beyond storage systems, any service that needs a small, strongly-consistent coordination substrate — leader election for a job scheduler, distributed locks, configuration propagation — typically reaches for Raft rather than re-deriving Paxos, precisely because its clear decomposition into leader election, log replication, and safety makes it feasible to implement, test, and audit correctly. This is the practical bridge from single-decree consensus theory to the everyday Replication and Consistency Models that back cloud infrastructure.

Ask the tutor: How does Raft's read-index protocol interact with leader leases to provide linearizable reads without a full quorum round trip on every request?

How does Raft differ fundamentally from Paxos if both provide the same guarantees?

Both tolerate `f` crash failures in a `2f+1` cluster and guarantee the same safety property: no two servers apply conflicting commands at the same log index. The difference is structural, not in the guarantee. Paxos is typically presented as a symmetric protocol among proposers and acceptors reaching agreement one value at a time, with leader election treated as a separate, loosely specified optimization (Multi-Paxos). Raft instead makes the leader a first-class, mandatory part of the core protocol: all client requests flow through one leader, which unilaterally decides log order. This turns "agree on one value" into "agree on the order of an entire log" as the primitive operation, and lets Raft use simple consistency checks (`prevLogIndex`/`prevLogTerm`) instead of a general ballot-numbering scheme, making the full protocol — including membership changes — far easier to specify completely and implement correctly.

What happens if the network partitions a Raft cluster into two groups?

Only the partition containing a majority of servers can elect a leader and commit new entries, because both leader election and commitment require majority acknowledgment. The minority partition's old leader (if it ends up there) will keep trying to replicate but never reach a majority, so it can never commit new entries; its followers' election timers will also fire, but they too can never gather a majority of votes. Once the partition heals, the minority servers' stale term numbers cause them to immediately step down to follower when they see the majority partition's higher term number in an `AppendEntries` or `RequestVote`, and their logs are reconciled via the normal `prevLogIndex`/`prevLogTerm` conflict-resolution mechanism, truncating any uncommitted divergent entries.

Can Raft lose committed data if enough servers crash simultaneously?

Yes, if more than `f` servers permanently fail in a `2f+1` cluster (i.e., a majority is lost), Raft — like any majority-based consensus protocol — cannot make further progress and, in the worst case of permanent data loss on those machines, could lose entries that were only replicated to the now-dead majority subset. This is not a design flaw; it is the fundamental limit of quorum-based consensus: durability requires a majority to survive. Production deployments mitigate this with geographic distribution of replicas, larger clusters (5 or 7 servers tolerating 2 or 3 failures), and periodic snapshotting to durable storage independent of the Raft log itself.

Why does Raft use randomized timeouts instead of a fixed election timeout?

If every follower used an identical fixed timeout, all followers would time out simultaneously whenever a leader fails, become candidates in the same instant, and split the vote evenly with high probability — repeating indefinitely in the worst case. Randomizing each server's timeout (typically uniformly between 150 ms and 300 ms) makes it overwhelmingly likely that exactly one follower's timer fires first; that server starts its election and sends `RequestVote` RPCs before any other follower's timer expires, so it collects a clean majority before any competing candidate even starts. This does not eliminate split votes entirely but reduces their probability enough that in practice elections resolve within one or two rounds.

How does log compaction interact with Raft's safety guarantees?

Left unbounded, the replicated log grows forever, consuming disk and slowing down new-follower catch-up. Raft addresses this with snapshotting: each server independently compacts its log by writing the current state machine state to a snapshot and discarding log entries up to that point. If a follower falls too far behind and the leader has already discarded the entries it needs, the leader instead sends an `InstallSnapshot` RPC transferring the whole state. Because a snapshot only ever includes committed entries, this does not weaken any safety guarantee — it is purely a storage optimization orthogonal to the commit and election protocols described above.

Does Raft guarantee linearizable reads, or only linearizable writes?

Writes are naturally linearizable because they go through the committed log. Naively reading from the leader's in-memory state is not automatically safe, because a server might believe it is still the leader (having been partitioned away) while a new leader has already been elected elsewhere — serving a stale read violates linearizability. Raft implementations fix this with a read-index protocol: before answering a read, the leader confirms its leadership is still valid by exchanging a fresh round of heartbeats with a majority (or holds a time-bounded leader lease), ensuring no newer leader could have been elected in the meantime. Only after that confirmation is the read served from the current state machine.

How does membership change (adding/removing servers) work safely in Raft?

Naively switching a majority-quorum definition from the old configuration directly to the new one is unsafe, because there can be a moment where a candidate wins a majority under the old configuration and a different candidate simultaneously wins a majority under the new one, producing two leaders in the same term. Raft's original solution is joint consensus: the cluster first commits a transitional configuration entry requiring a majority from both the old and new server sets simultaneously, and only after that entry is committed does it transition to the new configuration alone. A simpler alternative widely used in practice is the single-server-change restriction, which only ever adds or removes one server at a time, guaranteeing the old and new majorities always overlap.

Before this

Where this leads