system-design · intermediate

Consensus Algorithms — Agreeing Despite Failures

The Central Question

Consider a critical distributed metadata cluster running on the TxLab platform (txlab.com) processing 100,000,000 queries per day:


How do the remaining nodes (Node B, Node C, Node D) elect a new cluster leader automatically, guarantee that no two nodes act as leader simultaneously (Split-Brain Disasters), and ensure that committed state modifications are never lost or corrupted?

This fundamental challenge is solved by Distributed Consensus Algorithms.

A Consensus Algorithm is a protocol that allows a set of independent network nodes to agree on a single data value, execution order, or state transition log—even when some nodes fail, drop packets, or exhibit network latency.

This lesson answers one central question: How does the Raft consensus algorithm decompose distributed agreement into Leader Election, Log Replication, and Safety invariants, and how do systems leverage Quorum Majority ($Q = \lfloor \frac{N}{2} \rfloor + 1$) and Term Leases to maintain linearizable state safety in production?


Replicated State Machines (RSM)

Consensus algorithms are typically deployed to build Replicated State Machines (RSM):

flowchart TD
  Client[Application Client] -->|1. Submit Command| Leader[Leader Node]
  
  subgraph Replicated State Machine Cluster
    Leader -->|2. Write Command to Log| LeaderLog[Leader Consensus Log]
    LeaderLog -->|3. AppendEntries RPC| FollowerLog1[Follower 1 Log]
    LeaderLog -->|3. AppendEntries RPC| FollowerLog2[Follower 2 Log]
    
    LeaderLog -->|4. Apply to State Machine| SM1[Leader State Machine]
    FollowerLog1 -->|4. Apply to State Machine| SM2[Follower 1 State Machine]
    FollowerLog2 -->|4. Apply to State Machine| SM3[Follower 2 State Machine]
  end

Figure 1: Replicated State Machine architecture executing identical ordered logs across nodes.

The Core RSM Invariant

If two independent state machines start in the identical initial state and execute the identical sequence of deterministic commands from an ordered log, they will compute identical output states.

The Raft Consensus Protocol: 3 Core Sub-Problems

While Paxos was historically the dominant consensus protocol, its complexity made implementation notoriously difficult. In 2014, Diego Ongaro and John Ousterhout introduced Raft, which decomposes consensus into three clear sub-problems:

flowchart TD
  Raft[Raft Consensus Decomposition] --> Election[1. Leader Election]
  Raft --> Replication[2. Log Replication]
  Raft --> Safety[3. Safety Invariants]
  
  Election --> ElDesc["Select 1 Leader node per Term using Randomized Heartbeat Timeouts."]
  Replication --> RepDesc["Leader accepts writes from clients, appends to log, and replicates to Followers."]
  Safety --> SafeDesc["Enforce Election Safety, Leader-Only Writes, and Log Matching Properties."]

Figure 2: Taxonomy of Raft consensus sub-problems.

1. Node Roles in Raft

At any given moment, every node in a Raft cluster exists in one of three states:

Quorum Mathematics ($Q = \lfloor \frac{N}{2} \rfloor + 1$)

Raft requires a Quorum Majority of healthy nodes to elect a leader or commit a log entry:

$$Q = \left\lfloor \frac{N}{2} \right\rfloor + 1$$

For a cluster of $N = 5$ nodes, the quorum majority is:

$$Q = \left\lfloor \frac{5}{2} \right\rfloor + 1 = 2 + 1 = 3 \text{ nodes}$$

Cluster Fault Tolerance Table

Cluster Size ($N$)Quorum Majority ($Q$)Maximum Tolerated Node Failures ($F$)
3 Nodes2 Nodes1 Node
5 Nodes3 Nodes2 Nodes
7 Nodes4 Nodes3 Nodes
[!IMPORTANT] Why Production Clusters Use Odd Node Counts: A 4-node cluster requires $Q = 3$ nodes, tolerating only $F = 1$ failure ($4 - 3 = 1$). A 5-node cluster also requires $Q = 3$ nodes, but tolerates $F = 2$ failures. Adding a 4th node increases network overhead without improving fault tolerance! Always deploy consensus clusters with odd node counts (3, 5, or 7).

Raft Invariants and Log Safety

Raft guarantees correctness through five strict safety properties:

  1. Election Safety: At most one leader can be elected per term.
  2. Leader Append-Only: A leader never overwrites or truncates its own log entries; it only appends new entries.
  3. Log Matching Property: If two logs contain an entry with the same index and term, then the logs are identical in all entries up through the given index.
  4. Leader Completeness: If a log entry is committed in a given term, that entry will be present in the logs of the leaders for all higher-numbered terms.
  5. State Machine Safety: If a server has applied a log entry at a given index to its state machine, no other server will ever apply a different log entry for that index.

Pre-Vote Phase (Preventing Disrupted Leaders)

If a partitioned node (`Node E`) is disconnected from the cluster, its election timer continuously expires, incrementing its `currentTerm` to term 50. When `Node E` reconnects to the cluster, its high term forces the healthy Leader to step down, triggering unnecessary re-elections. To prevent this, Raft engines implement a **Pre-Vote Phase**:

Joint Consensus Configuration Membership Changes

When adding or removing nodes from a live Raft cluster (e.g. expanding from 3 to 5 nodes), naive configuration changes risk split-brain conditions where old and new quorums overlap incorrectly. Raft uses **Joint Consensus**:

Leader Leases for Zero-Latency Reads

To serve read queries without taking log replication consensus roundtrips on every `SELECT`, Raft leaders maintain a **Leader Lease**. As long as the leader receives heartbeat acknowledgments from a majority quorum within the lease window, it serves linearizable reads directly from local memory without invoking log replication.

Complete Worked Example: Production Go Raft Leader Election & Log Engine

Let's inspect a complete Go implementation of a Raft Leader Election & Log Engine for the TxLab platform (txlab.com).

package main

import (
"context"
"fmt"
"math/rand"
"sync"
"time"
)

type NodeRole int

const (
RoleFollower NodeRole = iota
RoleCandidate
RoleLeader
)

type RaftNode struct {
mu sync.Mutex
id int
currentTerm int64
votedFor int
role NodeRole
peers []*RaftNode
heartbeat chan bool
log []string
}

func NewRaftNode(id int) *RaftNode {
return &RaftNode{
id: id,
currentTerm: 0,
votedFor: -1,
role: RoleFollower,
heartbeat: make(chan bool, 10),
log: make([]string, 0),
}
}

func (n *RaftNode) Run(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
default:
n.mu.Lock()
role := n.role
n.mu.Unlock()

switch role {
case RoleFollower:
n.runFollower(ctx)
case RoleCandidate:
n.runCandidate(ctx)
case RoleLeader:
n.runLeader(ctx)
}
}
}
}

func (n RaftNode) runFollower(ctx context.Context) {
// Randomized Election Timeout (150ms - 300ms)
timeout := time.Duration(150+rand.Intn(150))
time.Millisecond
timer := time.NewTimer(timeout)
defer timer.Stop()

select {
case <-n.heartbeat:
// Reset timeout on heartbeat from Leader
case <-timer.C:
n.mu.Lock()
n.role = RoleCandidate
fmt.Printf("[ELECTION TIMEOUT] Node %d converted to CANDIDATE for Term %d\n", n.id, n.currentTerm+1)
n.mu.Unlock()
case <-ctx.Done():
return
}
}

func (n *RaftNode) runCandidate(ctx context.Context) {
n.mu.Lock()
n.currentTerm++
n.votedFor = n.id
term := n.currentTerm
n.mu.Unlock()

votes := 1
var wg sync.WaitGroup
var voteMu sync.Mutex

for _, peer := range n.peers {
wg.Add(1)
go func(p *RaftNode) {
defer wg.Done()
if p.RequestVote(n.id, term) {
voteMu.Lock()
votes++
voteMu.Unlock()
}
}(peer)
}

wg.Wait()

n.mu.Lock()
if votes >= (len(n.peers)+1)/2+1 {
n.role = RoleLeader
fmt.Printf("[ELECTION WON] Node %d elected LEADER for Term %d with %d votes!\n", n.id, term, votes)
} else {
n.role = RoleFollower
}
n.mu.Unlock()
}

func (n RaftNode) runLeader(ctx context.Context) {
fmt.Printf("[LEADER ACTIVE] Node %d sending heartbeats for Term %d...\n", n.id, n.currentTerm)
for _, peer := range n.peers {
peer.heartbeat <- true
}
time.Sleep(50
time.Millisecond)
}

func (n *RaftNode) RequestVote(candidateID int, term int64) bool {
n.mu.Lock()
defer n.mu.Unlock()

if term > n.currentTerm && (n.votedFor == -1 || n.votedFor == candidateID) {
n.currentTerm = term
n.votedFor = candidateID
return true
}
return false
}


Failure Modes and Engineering Mitigations

Failure ScenarioRoot CauseSystem SymptomDetection SignalMitigation Strategy
1. Split Vote Election Loop2 Candidate nodes request votes simultaneously; neither achieves a majority quorum.Election loops continuously without electing a leader; cluster writes block.Repeated term increment alerts without leader election.Use Randomized Election Timeouts (150ms-300ms) to stagger elections.
2. Split-Brain Dual LeaderNetwork partition isolates old leader; remaining nodes elect a new leader.Old leader accepts writes in minority partition that are later overwritten (Data Loss).Term mismatch logs between isolated cluster nodes.Enforce Quorum Majority Checks ($Q = \lfloor \frac{N}{2} \rfloor + 1$) on every write.
3. Flapping Leader DistractionIntermittent network packet loss causes followers to miss heartbeats and trigger unnecessary elections.Frequent leader re-elections degrade cluster throughput by 80%.High leader election rate metrics on monitoring dashboards.Increase Heartbeat Timeout Margins and use Pre-Vote phases.
4. Unbounded Log GrowthRaft consensus log appends millions of commands without compaction.Server memory runs out; node startup recovery takes hours scanning disk log.High Raft log byte size metrics on disk.Implement Log Snapshotting (Compaction) to prune committed entries.

What You Should Remember

  1. Raft decomposes consensus into 3 sub-problems: Leader Election, Log Replication, and Safety Invariants.
  2. Quorum Majority requires $Q = \lfloor \frac{N}{2} \rfloor + 1$: A 5-node cluster requires 3 node acknowledgments to elect leaders or commit logs.
  3. Deploy odd node counts: 3, 5, or 7 node clusters provide optimal fault tolerance without adding unnecessary network overhead.
  4. Randomize election timeouts: Stagger candidate election timers (150ms-300ms) to prevent split-vote election loops.
  5. Use Snapshot Compaction for log pruning: Compact committed Raft log entries into state machine snapshots to bound memory and startup recovery time.

Glossary of Terms

TermDefinition
Consensus AlgorithmA protocol enabling distributed nodes to agree on data values or state transitions despite failures.
Replicated State Machine (RSM)An architecture executing identical ordered command logs across nodes to maintain identical states.
RaftA consensus algorithm designed for understandability, decomposing agreement into election, replication, and safety.
Quorum MajorityThe minimum number of node votes ($\lfloor \frac{N}{2} \rfloor + 1$) required to elect a leader or commit entries.
TermA monotonically increasing integer in Raft acting as a logical clock to distinguish leader epochs.
Log SnapshottingThe process of compacting committed log entries into a state checkpoint to free disk and RAM space.

Practice Scenario and Self-Assessment

Architecture Scenario

You are designing the distributed lock manager for a cloud database platform (`locks.txlab.com`): **Questions**:
  1. Detail the step-by-step Raft election process triggered by Node 2, Node 3, Node 4, and Node 5 during Node 1's GC pause.
  2. Explain what happens when Node 1 completes its GC pause and attempts to issue writes under its old Term.

Interactive Self-Assessment

A 4-node cluster requires Q=3 nodes (tolerating F=1 failure), whereas a 5-node cluster also requires Q=3 nodes (tolerating F=2 failures). Adding an even node adds network overhead without increasing fault tolerance.

Even node counts automatically format persistent NVMe SSD disk drives on database servers.

Even node counts revoke client HTTPS TLS encryption certificates on edge load balancers.

Even node counts cut physical CPU hardware clock speeds in half across all consensus nodes.

It prevents Split Vote election loops by ensuring one follower times out first and collects majority votes before peers time out.

Randomized timeouts convert relational database primary key indexes into un-indexed CSV files.

Randomized timeouts replace public DNS nameservers with local hosts file entries.

Randomized timeouts reboot operating system hypervisors across all cluster nodes.


What to Learn Next

Track: Distributed Systems

By Shubham Jain

All articles · Study paths

Shubham Jain · Learning Lab