Building reliable distributed systems across unreliable networks and commodity servers is one of computer science's foundational challenges. The Raft consensus algorithm, designed by Diego Ongaro and John Ousterhout at Stanford University (2014), was formulated to provide formal safety and fault-tolerance guarantees strictly equivalent to Multi-Paxos while emphasizing operational comprehensibility, structural decomposition, and deterministic formal invariants.
In this comprehensive architectural guide, we dissect Raft's three decomposed subproblems: Leader Election, Log Replication, and Safety & Invariant Proofs. We explore cluster membership changes via Joint Consensus, state machine log compaction via snapshotting, and evaluate real-world production edge cases including network partitions, split-brain scenarios, and the Pre-Vote protocol.
The Replicated State Machine Architecture#
Distributed consensus algorithms operate on the paradigm of Replicated State Machines (RSMs). In an RSM, identical deterministic state machines execute independently on multiple networked server nodes. If each replica receives and executes the exact same sequence of state transitions starting from the identical initial state, then all replicas will arrive at identical final states.
+------------+ +-------------------+ +---------------+
| Client | ----> | Consensus Module | ----> | Replicated Log|
+------------+ +-------------------+ +---------------+
|
v
+---------------+
| State Machine |
+---------------+The consensus protocol acts as an ordering coordinator:
- It ingests client command requests at the current cluster leader.
- It assigns an incrementally monotonic Log Index and discrete Term Number to each command.
- It ensures that whenever a log entry is declared Committed, it will permanently persist across all future cluster leaders despite arbitrary crash-stop or crash-recovery node failures.
Server Roles and Term Epoch Mechanics#
At any moment in time, each server node in a Raft cluster resides in exactly one of three states:
- Leader: Serves all incoming client read and write requests, drives log replication to followers, and maintains authority by broadcasting periodic heartbeat RPCs.
- Follower: Completely passive participant. Responds strictly to incoming Remote Procedure Calls (RPCs) from candidates and leaders. If no communication is received within an election timeout window, it transitions to candidate state.
- Candidate: Used during elections to contest leadership, collect quorum votes from peer nodes, and establish a new term.
[ Election timeout fires, starts election ]
+----------------------------------------------------> [ Candidate ]
| |
| | [ Wins majority quorum ]
v v
[ Follower ] <----------------------------------------- [ Leader ]
[ Discovers higher term or valid current leader ]Logical Clock Epochs: Raft Terms#
Raft divides time into discrete, monotonically increasing integer Terms (Term 1, Term 2, Term 3...). Terms act as logical clocks, enabling servers to detect and discard stale leaders:
- Each server stores a persistent variable
currentTermon non-volatile disk. - When an election begins, the candidate increments
currentTerm. - If a server receives an RPC containing a term t <
currentTerm, it rejects the RPC withSuccess = false. - If a server discovers a peer with a term t >
currentTerm, it immediately updates its owncurrentTerm = tand steps down to the follower state.
Leader Election & Randomized Timeout Physics#
To achieve fault tolerance, a Raft cluster of N servers requires a Majority Quorum to elect a leader or commit log entries:
Quorum Size Q = ⌊N / 2⌋ + 1For a 5-node cluster, Q = ⌊5 / 2⌋ + 1 = 2 + 1 = 3 servers. The system can safely tolerate up to f = ⌊(N - 1) / 2⌋ = 2 simultaneous node failures without losing availability.
+-------------------------------------------------------------+
| Quorum Intersection Property |
+-------------------------------------------------------------+
| Quorum A (e.g., S1, S2, S3) Quorum B (e.g., S3, S4, S5) |
| \ / |
| \ / |
| +-----> [ Overlap: S3 ] <----+ |
+-------------------------------------------------------------+Because any two majority quorums must intersect at least on one shared server (by the Pigeonhole Principle), at least one node in any newly formed quorum possesses the latest committed log entry.
Randomized Election Timeouts#
If multiple followers simultaneously detect a leader failure, they might all increment their terms, transition to candidate state, and request votes at the exact same millisecond. This leads to a Split Vote, where votes are fragmented across multiple candidates and no node achieves majority quorum.
To prevent split-vote livelocks, Raft employs Randomized Election Timeouts:
- Each node selects a timeout duration uniformly at random from a configurable interval (typically 150 ms to 300 ms).
- The node with the shortest timeout wakes up first, increments
currentTerm, casts a self-vote, and broadcastsRequestVoteRPCs. - In practice, the first candidate collects a majority quorum and establishes leadership before competitors time out, virtually eliminating split-vote collisions.
Log Replication & Consistency Invariants#
When a client submits a state machine command to the leader:
- Local Append: The leader appends the entry to its local log. The entry contains
(Index, Term, Command). - Parallel Replication: The leader issues
AppendEntriesRPCs to all follower nodes. - Quorum Acknowledgment: Once a majority quorum (Q nodes) acknowledges receipt, the entry is marked as Committed.
- State Machine Execution: The leader applies the committed entry to its local state machine and returns the execution result to the client.
- Follower Notification: Subsequent heartbeats notify followers of the updated
leaderCommitindex, prompting followers to execute the entry against their local state machines.
// Production Go representation of Raft RPC Wire Structures
type LogEntry struct {
Index uint64 // 1-based log index
Term uint64 // Election term when entry was received
Command []byte // Serialized state machine instruction
}
type AppendEntriesArgs struct {
Term uint64 // Leader's current term
LeaderId string // Identifier of leader so followers can redirect clients
PrevLogIndex uint64 // Index of log entry immediately preceding new ones
PrevLogTerm uint64 // Term of entry at PrevLogIndex
Entries []LogEntry // Log entries to replicate (empty for periodic heartbeat)
LeaderCommit uint64 // Leader's committed index
}
type AppendEntriesReply struct {
Term uint64 // Current term of recipient, for leader to update itself
Success bool // True if follower found matching PrevLogIndex and PrevLogTerm
}The Log Matching Property#
Raft maintains two core log consistency invariants:
- If two entries in different logs have the identical index and term, they store the exact same command.
- If two entries in different logs have the identical index and term, then their logs are completely identical in all preceding entries from index 1 up to index.
Inductive Consistency Check: When a follower receives an AppendEntries RPC, it checks its local log at PrevLogIndex. If it has no entry at that index, or if the term does not match PrevLogTerm, it rejects the RPC. If accepted, the follower overwrites any subsequent conflicting uncommitted entries with the leader's log, guaranteeing convergence.
Formal Safety & Leader Completeness Proof#
Raft guarantees that if any server applies a log entry at a given index to its state machine, no other server will ever apply a different log entry for that index. This relies on the Leader Completeness Property:
> Leader Completeness Theorem: If a log entry is committed in a given term, then that entry will be present in the logs of the leaders for all higher-numbered terms.
Election Restriction Voting Rule#
To enforce Leader Completeness without requiring followers to transfer their logs to a new leader, Raft restricts which candidates can win an election:
When a follower receives a RequestVote(candidateId, candidateTerm, lastLogIndex, lastLogTerm) RPC, it denies its vote unless the candidate's log is at least as up-to-date as the follower's own log:
- If
candidate.lastLogTerm != follower.lastLogTerm: The candidate with the higherlastLogTermis more up-to-date. - If
candidate.lastLogTerm == follower.lastLogTerm: The candidate with the largerlastLogIndex(longer log) is more up-to-date.
Proof of Safety by Contradiction:
- Assume entry e is committed in term T at index i by leader L_T.
- By definition of commitment, e is stored on a majority quorum of servers Q_commit.
- Suppose some leader L_U for a higher term U > T is elected without entry e.
- To win the election, L_U must collect votes from a majority quorum of servers Q_vote.
- Because Q_commit and Q_vote are both majorities of the same cluster, their intersection is non-empty: there exists at least one server S ∈ (Q_commit ∩ Q_vote).
- Because S accepted entry e in term T, its log contains e at index i, so S.lastLogTerm ≥ T.
- Candidate L_U does not contain e, so its log is strictly less up-to-date than S.
- Therefore, according to the Election Restriction rule, server S must deny its vote to L_U.
- Consequently, L_U could not have received votes from Q_vote, contradicting the assumption that L_U was elected leader.
- Hence, no leader without entry e can ever be elected.
Network Partitions, Split-Brain & Pre-Vote#
Consider an industrial 5-node cluster {S1, S2, S3, S4, S5} deployed across two data centers. A network partition isolates the cluster into two network segments:
- Minority Segment: {S1 (Leader), S2}
- Majority Segment: {S3, S4, S5}
Minority Partition (2 nodes) Majority Partition (3 nodes)
+-------------------------+ +-------------------------+
| S1 (Old Leader, Term 1) | | S3 (New Leader, Term 2) |
| S2 (Follower, Term 1) | | S4 (Follower, Term 2) |
+-------------------------+ | S5 (Follower, Term 2) |
X +-------------------------+
X <--- Network Partition DisconnectMinority Behavior#
When a client submits write requests to S1 in the minority partition:
- S1 appends the entry locally and attempts to replicate to peers.
- S1 only reaches S2. Quorum requires 3 nodes. S1 cannot reach quorum (2 / 5 < 3).
- S1 leaves the entry uncommitted and never acknowledges success to the client, preventing split-brain corruption.
Majority Behavior#
In the majority partition, nodes S3, S4, and S5 observe a heartbeat timeout from S1:
- S3 initiates an election for Term 2.
- S3 collects votes from S4 and S5 (3 out of 5 nodes = majority quorum).
- S3 becomes the authoritative leader for Term 2 and successfully commits client writes.
Partition Healing & Log Reconciliation#
When the network partition heals:
- S1 sends a heartbeat to S3 with Term 1.
- S3 rejects the heartbeat because Term 1 < Term 2. S3 replies with Term 2.
- S1 discovers the higher term (Term 2 > Term 1), immediately steps down to follower state, and updates its term to 2.
- S3 issues
AppendEntriesto S1 and S2. - S1 overwrites its uncommitted, stale entries with S3's authoritative log, restoring cluster-wide consistency.
The Disruptive Server Problem & The Pre-Vote Protocol#
In standard Raft, if a single server S_isolated is partitioned from the cluster:
- S_isolated continuously times out, increments its term (Term 3, 4, 5, ... 10), and broadcasts vote requests.
- When the network reconnects, S_isolated sends an RPC with Term 10 to the healthy cluster.
- The healthy leader discovers Term 10 > Term 2 and immediately steps down, triggering an unnecessary, disruptive election across the entire cluster.
The Pre-Vote Fix: Before incrementing its persistent term, a candidate enters a speculative Pre-Vote phase. It asks peers: "Would you vote for me if I started an election?" Peers only respond affirmatively if they have also timed out from the current leader. Because healthy peers are actively receiving heartbeats from the leader, they reject the Pre-Vote. The isolated node cannot increment its term or disrupt the cluster.
Checkpoint Practice Problems#
Apply the formal consensus mechanics, quorum arithmetic, and failure recovery protocols.
Problem 1: Quorum and Fault-Tolerance Bounds#
Prompt: An enterprise infrastructure engineer proposes deploying a Raft consensus cluster across 6 commodity servers instead of 5 servers to "increase reliability".
- Calculate the majority quorum size Q for a 5-node cluster versus a 6-node cluster.
- Determine how many simultaneous node crash failures each cluster can tolerate while remaining available for client writes.
- Explain why even-numbered node clusters (e.g., 6 nodes) are an anti-pattern in distributed systems.
Solution:
- Using the majority quorum formula Q = ⌊N / 2⌋ + 1:
- For N = 5: Q = ⌊5 / 2⌋ + 1 = 2 + 1 = 3 nodes.
- For N = 6: Q = ⌊6 / 2⌋ + 1 = 3 + 1 = 4 nodes.
- Failure tolerance calculation f = ⌊(N - 1) / 2⌋:
- For N = 5: f = ⌊(5 - 1) / 2⌋ = 4 / 2 = 2 nodes.
- For N = 6: f = ⌊(6 - 1) / 2⌋ = 5 / 2 = 2 nodes.
- Why 6 nodes is an anti-pattern:
- A 6-node cluster tolerates exactly 2 failures—the exact same fault-tolerance as a 5-node cluster.
- However, the 6-node cluster requires 4 acknowledgments to commit an entry instead of 3, increasing network latency and probability of RPC failure.
- Furthermore, in a 50/50 network partition (3 nodes vs 3 nodes), neither side achieves 4 votes, completely paralyzing the cluster. A 5-node cluster partitions into 3 vs 2, allowing the 3-node partition to remain operational.
Problem 2: Log Reconciliation Execution Trace#
Prompt: Consider the following log states for Leader L and Follower F at the beginning of Term 4:
- Leader L log:
[(Index 1, Term 1), (Index 2, Term 1), (Index 3, Term 2), (Index 4, Term 3), (Index 5, Term 4)] - Follower F log:
[(Index 1, Term 1), (Index 2, Term 1), (Index 3, Term 2), (Index 4, Term 2)] - Leader's
nextIndex[F]is initialized to 6 (the leader's last log index + 1).
Trace the step-by-step RPC interaction between Leader L and Follower F until the follower's log is synchronized.
Solution:
- Round 1:
- Leader L sends AppendEntries with PrevLogIndex = 5, PrevLogTerm = 4, Entries = [].
- Follower F inspects its log at Index 5. Follower F has no entry at Index 5.
- Follower F replies with Success = false.
- Round 2:
- Leader L decrements nextIndex[F] from 6 to 5.
- Leader L sends AppendEntries with PrevLogIndex = 4, PrevLogTerm = 3, Entries = [(Index 5, Term 4)].
- Follower F inspects its log at Index 4. Follower F has (Index 4, Term 2).
- Term mismatch: Follower's term (2) ≠ Leader's PrevLogTerm (3).
- Follower F replies with Success = false.
- Round 3:
- Leader L decrements nextIndex[F] from 5 to 4.
- Leader L sends AppendEntries with PrevLogIndex = 3, PrevLogTerm = 2, Entries = [(Index 4, Term 3), (Index 5, Term 4)].
- Follower F inspects its log at Index 3. Follower F has (Index 3, Term 2).
- Match confirmed: PrevLogTerm matches.
- Follower F overwrites its conflicting entry at Index 4 with (Index 4, Term 3) and appends (Index 5, Term 4).
- Follower F replies with Success = true.
- Follower F's log is now identical to Leader L.
Problem 3: The Danger of Committing Prior-Term Entries Directly#
Prompt: In Figure 8 of the original Raft paper, Ongaro & Ousterhout demonstrate that a leader cannot directly commit a log entry from an earlier term by counting replicas alone. State the rule Raft uses to commit log entries from previous terms safely.
Solution:
- The Core Rule: A leader never commits a log entry from a previous term simply by counting its replicas. A leader only commits log entries from its current term by counting replicas to reach a majority quorum.
- Once an entry from the current term has been committed by counting a majority, all prior entries are committed indirectly due to the Log Matching Property.
- Why this rule is necessary: If a leader could count replicas to commit old entries, an alternate leader could be elected by a separate quorum that had not observed an overwrite of that entry, causing previously committed entries to be replaced and violating State Machine Safety.
Problem 4: Election Invariant Verification#
Prompt: Given three nodes with the following logs:
- Node A:
[(Term 1, Index 1), (Term 2, Index 2), (Term 3, Index 3)] - Node B:
[(Term 1, Index 1), (Term 2, Index 2)] - Node C:
[(Term 1, Index 1), (Term 1, Index 2), (Term 1, Index 3), (Term 1, Index 4)]
Determine which nodes can win an election if they request votes from the other two nodes.
Solution:
- Candidate A:
- lastLogTerm = 3, lastLogIndex = 3.
- Compared to B (lastLogTerm = 2): A's term (3) > B's term (2). B votes for A.
- Compared to C (lastLogTerm = 1): A's term (3) > C's term (1). C votes for A.
- Candidate A receives votes from A, B, and C (3/3 votes) and wins the election.
- Candidate B:
- lastLogTerm = 2, lastLogIndex = 2.
- Compared to A: B's term (2) < A's term (3). A rejects B.
- Compared to C: B's term (2) > C's term (1). C votes for B.
- Candidate B only receives votes from B and C (2/3 votes). While 2 is a majority, in a 3-node cluster with A contesting, A takes precedence.
- Candidate C:
- lastLogTerm = 1, lastLogIndex = 4.
- Despite having 4 entries (longer log), C's lastLogTerm is only 1.
- Both Node A (Term 3) and Node B (Term 2) have higher last log terms and will reject C's vote request.
- Candidate C receives only 1 vote (self) and cannot win.