Introduction

Imagine five servers that need to agree on every operation โ€” every database write, every configuration change โ€” even though any server can crash at any moment, and messages between them can be delayed or lost. This is the consensus problem, and it is one of the most fundamental challenges in distributed systems.

For years, Paxos was the standard answer. It works, and it is provably correct โ€” but it is notoriously hard to understand, and harder still to implement correctly. Real-world systems built on Paxos often required the designers to invent their own extensions just to fill in the gaps the original paper left open.

In 2014, Diego Ongaro and John Ousterhout published Raft โ€” an algorithm designed from the ground up with one explicit goal: understandability. They decomposed the consensus problem into three nearly independent sub-problems: leader election, log replication, and safety. The result is an algorithm that engineers can actually hold in their heads and implement correctly.

Raft is now the consensus engine behind etcd (which powers Kubernetes), CockroachDB, TiKV, and dozens of other production systems. It is the algorithm you reach for when you need distributed agreement and you want to be able to reason about what your code is actually doing.

Elect and Replicate

The demo below shows a five-node Raft cluster. Each node is either a Leader (gold), Follower (blue), or Candidate (amber). Click Start Election to kick off a leader election, then Append Entry to add a log entry that replicates to all followers. Try Crash Follower to drop a node and watch the cluster keep working.

<p class="hint">A five-node Raft cluster. Each node is a <b>Leader</b> (gold), <b>Follower</b> (blue), or <b>Candidate</b> (amber). Click the buttons to run an election, append a log entry, or crash a follower.</p>
<div id="cluster"></div>
<div id="log-panel">
  <div class="log-title">Replicated Log</div>
  <div id="log-entries"></div>
</div>
<div class="status" id="status">Click "Start Election" to begin.</div>
<div class="btns">
  <button id="btn-elect" type="button">Start Election</button>
  <button id="btn-append" type="button">Append Entry</button>
  <button id="btn-crash" type="button" class="ghost">Crash Follower</button>
  <button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
#cluster { display: flex; gap: 10px; margin: .5rem 0 .8rem; flex-wrap: wrap; }
.node { width: 80px; padding: 8px 4px; border-radius: 10px; text-align: center;
        border: 2px solid transparent; transition: all .25s; user-select: none; }
.node-name { font: 700 13px ui-monospace, monospace; margin-bottom: 3px; }
.node-role { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; }
.node-term { font-size: 11px; color: #666; margin-top: 2px; }
.node.leader { background: #ffe680; border-color: #c8a000; }
.node.leader .node-role { color: #7a5f00; }
.node.follower { background: #dbeafe; border-color: #3b82f6; }
.node.follower .node-role { color: #1e40af; }
.node.candidate { background: #fde68a; border-color: #d97706; }
.node.candidate .node-role { color: #92400e; }
.node.crashed { background: #f1f5f9; border-color: #94a3b8; opacity: .55; }
.node.crashed .node-role { color: #64748b; }
#log-panel { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px;
             padding: 8px 10px; margin-bottom: .7rem; min-height: 44px; }
.log-title { font: 700 12px system-ui; color: #64748b; text-transform: uppercase;
             letter-spacing: .06em; margin-bottom: 6px; }
#log-entries { display: flex; gap: 6px; flex-wrap: wrap; }
.log-entry { padding: 4px 8px; border-radius: 6px; font: 600 12px ui-monospace, monospace;
             border: 1.5px solid #3b82f6; background: #eff6ff; color: #1d4ed8; }
.log-entry.committed { border-color: #16a34a; background: #f0fdf4; color: #166534; }
.status { font-size: .95rem; font-weight: 600; margin: .4rem 0; min-height: 1.4em; }
.status.ok { color: #166534; }
.status.warn { color: #92400e; }
.status.info { color: #1e40af; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .3rem; }
button { font: 600 13px system-ui; padding: .42rem .85rem; border-radius: 8px; cursor: pointer;
         border: 1.5px solid #1d3557; background: #1d3557; color: #fff; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .45; cursor: default; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Raft consensus demo: leader election + log replication across 5 nodes.
// Simplified: network is instantaneous; terms increment on each election.
// A majority = 3 of 5 nodes needed to commit entries or elect a leader.

const TOTAL = 5;
const MAJORITY = 3;

let state = null;

function initState() {
  return {
    nodes: Array.from({ length: TOTAL }, (_, i) => ({
      id: i,
      name: 'N' + (i + 1),
      role: 'follower',   // 'leader' | 'follower' | 'candidate' | 'crashed'
      term: 0,
      votedFor: null,
      log: []             // array of { index, term, value, committed }
    })),
    term: 0,
    leaderId: null,
    nextEntry: 1          // next log value to propose
  };
}

function aliveNodes(s) {
  return s.nodes.filter(n => n.role !== 'crashed');
}

// โ”€โ”€ Render โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function render(s) {
  const cluster = document.getElementById('cluster');
  cluster.innerHTML = '';
  s.nodes.forEach(n => {
    const div = document.createElement('div');
    div.className = 'node ' + n.role;
    div.innerHTML =
      fmt('<div class="node-name">%name%</div><div class="node-role">%role%</div><div class="node-term">term %term%</div>', { name: n.name, role: n.role, term: n.term });
    cluster.appendChild(div);
  });

  // Show leader's log (or first alive node's if no leader)
  const src = s.leaderId !== null ? s.nodes[s.leaderId]
            : (aliveNodes(s)[0] || s.nodes[0]);
  const logEl = document.getElementById('log-entries');
  logEl.innerHTML = '';
  src.log.forEach(e => {
    const d = document.createElement('div');
    d.className = 'log-entry' + (e.committed ? ' committed' : '');
    d.textContent = '[' + e.index + '] op:' + e.value;
    logEl.appendChild(d);
  });
}

function setStatus(text, cls) {
  const el = document.getElementById('status');
  el.textContent = text;
  el.className = 'status ' + (cls || '');
}

// โ”€โ”€ Election โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function runElection(s) {
  const alive = aliveNodes(s);
  if (alive.length < MAJORITY) {
    setStatus(fmt('Not enough nodes for an election (need %MAJORITY%).', { MAJORITY: MAJORITY }), 'warn');
    return;
  }
  // Pick a random candidate from followers
  const followers = alive.filter(n => n.role === 'follower');
  if (followers.length === 0 && s.leaderId !== null) {
    setStatus(fmt('Already have a leader: %name%.', { name: s.nodes[s.leaderId].name }), 'info');
    return;
  }
  const candidate = followers.length > 0
    ? followers[Math.floor(Math.random() * followers.length)]
    : alive[Math.floor(Math.random() * alive.length)];

  s.term += 1;
  candidate.role = 'candidate';
  candidate.term = s.term;
  candidate.votedFor = candidate.id;

  // Gather votes from alive nodes
  let votes = 1; // candidate votes for itself
  alive.forEach(n => {
    if (n.id === candidate.id) return;
    // In real Raft, a node only votes if candidate's log is at least as up-to-date.
    // Here we simplify: if not voted yet in this term, vote yes.
    if (n.term < s.term) {
      n.term = s.term;
      n.votedFor = candidate.id;
      votes++;
    }
  });

  if (votes >= MAJORITY) {
    // Won election
    s.leaderId = candidate.id;
    s.nodes.forEach(n => {
      if (n.role === 'crashed') return;
      if (n.id === candidate.id) {
        n.role = 'leader';
        // Copy leader's log to followers (simplified: all followers sync)
      } else {
        n.role = 'follower';
        n.log = [...candidate.log]; // followers mirror leader log
      }
    });
    setStatus(fmt('%name%  won election with %votes%/%TOTAL% votes (term %term%).', { name: candidate.name, votes: votes, TOTAL: TOTAL, term: s.term }), 'ok');
  } else {
    candidate.role = 'follower';
    setStatus(fmt('Election failed โ€” %name% got %votes% votes, needed %MAJORITY%.', { name: candidate.name, votes: votes, MAJORITY: MAJORITY }), 'warn');
  }
  render(s);
}

// โ”€โ”€ Append Entry โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function appendEntry(s) {
  if (s.leaderId === null || s.nodes[s.leaderId].role !== 'leader') {
    setStatus('No leader yet โ€” run an election first.', 'warn');
    return;
  }
  const leader = s.nodes[s.leaderId];
  const entry = { index: s.nextEntry, term: s.term, value: s.nextEntry, committed: false };
  s.nextEntry++;

  // Append to leader
  leader.log.push(entry);

  // Replicate to alive followers
  const alive = aliveNodes(s).filter(n => n.id !== leader.id);
  alive.forEach(n => n.log.push({ ...entry }));

  // Count acknowledgements (leader + alive followers)
  const acks = 1 + alive.length; // all alive nodes replicated immediately
  if (acks >= MAJORITY) {
    // Commit on all nodes
    s.nodes.forEach(n => {
      const e = n.log.find(x => x.index === entry.index);
      if (e) e.committed = true;
    });
    setStatus(fmt('Entry %index% replicated to %v%%length% nodes and committed.', { index: entry.index, v: 1, length: alive.length }), 'ok');
  } else {
    setStatus(fmt('Entry %index% appended but not yet committed (%acks%/%MAJORITY% acks).', { index: entry.index, acks: acks, MAJORITY: MAJORITY }), 'warn');
  }
  render(s);
}

// โ”€โ”€ Crash Follower โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function crashFollower(s) {
  const followers = s.nodes.filter(n => n.role === 'follower');
  if (followers.length === 0) {
    setStatus('No followers to crash.', 'warn');
    return;
  }
  const victim = followers[Math.floor(Math.random() * followers.length)];
  victim.role = 'crashed';
  const alive = aliveNodes(s).length;
  const msg = alive >= MAJORITY
    ? fmt('%name% crashed. %alive% nodes alive โ€” cluster still has majority.', { name: victim.name, alive: alive })
    : fmt('%name% crashed. Only %alive% nodes alive โ€” below majority, writes blocked!', { name: victim.name, alive: alive });
  setStatus(msg, alive >= MAJORITY ? 'ok' : 'warn');
  render(s);
}

// โ”€โ”€ Init โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
state = initState();
render(state);

document.getElementById('btn-elect').addEventListener('click', () => runElection(state));
document.getElementById('btn-append').addEventListener('click', () => appendEntry(state));
document.getElementById('btn-crash').addEventListener('click', () => crashFollower(state));
document.getElementById('btn-reset').addEventListener('click', () => {
  state = initState();
  render(state);
  setStatus('Click "Start Election" to begin.', '');
});
Result
Console

Notice the key insight: as long as a majority of nodes (three out of five here) are alive and can communicate, the cluster remains available. The leader replicates each entry to followers, and an entry is committed only once a majority has acknowledged it โ€” guaranteeing it survives any minority of crashes.

The Real Complexity

Raft looks clean, but it lives inside some deep impossibility results.

  • The FLP impossibility (1985): Fischer, Lynch, and Paterson proved that in an asynchronous network โ€” where messages can be delayed by any finite amount โ€” no deterministic consensus algorithm can always make progress. Every consensus protocol, including Raft, handles this by assuming that delays are usually bounded (eventually synchronous), and by timing out when they are not.
  • The CAP theorem: When a network partition splits a cluster into two halves, a system must choose between consistency (all nodes see the same data) and availability (every request gets a response). Raft chooses consistency: if a leader cannot reach a majority of nodes, it stops accepting writes. The minority side simply waits.
  • Safety is formally proven: Raft's key safety property โ€” the Log Matching Property โ€” guarantees that if two logs contain an entry with the same index and term, then all entries up to that point are identical. This was verified formally with the TLA+ model checker, giving engineers strong guarantees beyond what testing alone provides.
  • Liveness requires timing: Raft makes progress only when the network is eventually synchronous and election timeouts are chosen well. A leader whose heartbeats are consistently slower than followers' timeouts will trigger repeated re-elections. In practice, timeouts of 150โ€“300 ms work in most datacenters.

Raft did not make consensus easy โ€” it made it understandable. The hard limits still apply: you cannot avoid the cost of a round-trip to a majority, and you cannot guarantee progress under arbitrary network behavior. What Raft gives you is a clean mental model for reasoning about those tradeoffs, and a proven-correct implementation path. Compare this to P vs NP โ€” another boundary where a problem is tractable to verify but the full solution space hides irreducible cost.

Where It Matters

Raft's understandability premium has made it the go-to consensus algorithm for a generation of distributed systems:

  • Kubernetes via etcd: etcd is the distributed key-value store that holds all of Kubernetes' cluster state โ€” which pods are running, which nodes exist, which services are configured. It uses Raft to ensure that this state is consistent across all etcd replicas, even if some crash.
  • CockroachDB and TiKV: These distributed SQL databases use Raft (one instance per shard) to replicate each range of data. Every write goes through Raft, giving the database the same strong consistency guarantees as a single machine while surviving node failures.
  • Service discovery and configuration: Systems like Consul use Raft to maintain consistent configuration data across a cluster, ensuring all services see the same view of which endpoints are healthy.
  • Distributed locks and coordination: Any time multiple services need to coordinate โ€” electing a primary, acquiring a distributed lock, or performing a two-phase commit โ€” Raft provides the reliable ordering guarantee that makes it safe.

The common thread: whenever you need a group of machines to behave as reliably as a single machine, but without a single point of failure, Raft is the mechanism that makes it possible. See also load balancing for how work gets distributed once consensus keeps the cluster in sync.

Conclusion

Raft did not discover new theoretical ground โ€” Paxos already solved distributed consensus. What Raft discovered is that understandability is a correctness property. An algorithm engineers cannot understand is an algorithm they will implement wrong, and a wrong consensus algorithm is far worse than a slow one: it silently corrupts your data while appearing to work.

By decomposing the problem into leader election, log replication, and safety โ€” and by making every design choice explicit and motivated โ€” Raft gave engineers something Paxos never quite managed: a system they could trust because they could reason about it.

The FLP impossibility and CAP theorem are still there. You still pay the cost of a round-trip to a majority. You still stop writing during a partition. But now you know exactly why, and exactly what your system will do. In distributed systems, that kind of clarity is worth more than most optimizations. See also Nash equilibrium for another domain where the right framing of a problem changes what becomes solvable.

Share this article

Pick a channel โ€” or use your device's native share sheet.

Comments

Loading comments...

https://www.kipuhub.com/en/article/raft/Content licensed under CC BY-NC 4.0.