Skip to content
FoundationVerified Implementation

Raft Distributed Consensus

Maintains a replicated state-machine log across a cluster via leader election (randomized 150–300ms timeouts, §5.4.1 up-to-date log check) and majority-quorum AppendEntries replication.

Quick Facts

Compact metadata for implementation and discovery.

Category
Distributed Consensus
Difficulty
Advanced
Family
Distributed Consensus
Tier
Foundation
Status
Verified
Review
Source Checked
Sources
1
Formulas
1
Relationships
3
Code example
Ready
Visualization
Ready
Aliases
Raft Protocol, Replicated State Machine Consensus, Paxos / Raft Consensus

Overview

Raft is a crash-fault-tolerant distributed consensus protocol designed as a decomposition of Paxos. It partitions consensus into three subproblems—Leader Election, Log Replication, and Safety—ensuring that a cluster of 2f + 1 servers agrees on an ordered sequence of state-machine commands even if up to f servers fail.

Core Concepts

monotonic term numbersmajority quorum intersectionup-to-date log election restrictionleader completeness

Quick facts

Complexity

Runtime and memory behavior at a glance.

Best

O(1) RTT

Average

O(1) RTT per commit

Worst

O(N) messages per heartbeat

Space

O(log length)

Interactive Runtime & Memory Budget Calculator

Estimate wall-clock latency, operation count, and working memory for Raft Distributed Consensus across input scales.

Input Scale N:
JIT-compiled JS/TS (~350M primitive ops/sec)
Est. Average TimeO(1) RTT per commit

11 ns

Sub-microsecond~4
Est. Worst-Case TimeO(N) messages per heartbeat

428.6 µs

60 FPS Frame-Safe (<16ms)~150.0k ops
Est. Working MemoryO(log length)

1.0 KB

L2/L3 Cache FriendlyN=100,000

When to Use

  • Understandable distributed consensus and replicated state machine protocol using leader election, randomized timeouts, RequestVote log checks, AppendEntries log replication, and majority quorum commits across replicated servers.
  • Use Raft when you need strong linearizable consensus or leader election among replicated servers tolerating crash and network partition faults.

When Not to Use

  • Avoid Raft when nodes may act maliciously/Byzantine (use PBFT or HotStuff) or when eventual consistency (gossip/CRDTs) suffices.

What It Does

  • Elects a single leader per monotonically increasing term via RequestVote quorum
  • Replicates client log entries from the leader via AppendEntries heartbeats
  • Commits entries once replicated on a strict majority (floor(N/2) + 1) of servers

Use Cases

  • Consensus among replicated servers in distributed KV stores (etcd, CockroachDB, Consul, TiKV)
  • Leader election and configuration management in cluster control planes
  • Fault-tolerant replicated state machines

Inputs

  • Cluster node states, current term, replicated log entries, and RPC messages (RequestVote, AppendEntries)

Outputs

  • Linearizable committed log prefix and elected cluster leader

Key Formulas (1)

Crash-Fault Quorum

Q = \lfloor N / 2 \rfloor + 1,\quad N = 2f + 1
Q = \lfloor N / 2 \rfloor + 1,\quad N = 2f + 1

Any two majority quorums intersect in at least one server.

Pseudocode

  1. 1.Follower converts to Candidate if election timeout elapses without leader AppendEntries
  2. 2.Candidate increments currentTerm, votes for self, and broadcasts RequestVote(term, lastLogIndex, lastLogTerm)
  3. 3.Voter grants vote iff candidateTerm >= currentTerm, not yet voted in term, and candidate log is at least as up-to-date
  4. 4.Candidate receiving majority votes (> N/2) becomes Leader and sends AppendEntries heartbeats
  5. 5.Leader advances commitIndex to N once a majority of matchIndex[i] >= N and log[N].term == currentTerm

Verified Implementation & Unit Tests

Verified Implementation

Tested TypeScript and Python 3 implementations paired with unit tests covering edge cases, duplicate keys, and boundary conditions.

/**
 * Raft Distributed Consensus State Machine (Ongaro & Ousterhout, 2014)
 * Implements monotonic currentTerm, votedFor persistence, randomized election timeouts (150..300ms),
 * §5.4.1 RequestVote log completeness checks, leader nextIndex[]/matchIndex[] tracking with backoff on rejection,
 * AppendEntries log replication with Figure 2 commitIndex safety (min(leaderCommit, prevLogIndex + entries.length)),
 * and §5.4.2 (Figure 8) leader quorum commit rule (only committing entries from currentTerm by counting replicas).
 */
export interface LogEntry { term: number; command: string; }
export interface LogMeta { lastLogTerm: number; lastLogIndex: number; }

export function isCandidateLogUpToDate(candidate: LogMeta, voter: LogMeta): boolean {
  if (candidate.lastLogTerm !== voter.lastLogTerm) {
    return candidate.lastLogTerm > voter.lastLogTerm;
  }
  return candidate.lastLogIndex >= voter.lastLogIndex;
}

export function evaluateRaftElectionQuorum(
  clusterSize: number,
  voterLogs: readonly LogMeta[],
  candidateLog: LogMeta
): { quorumReached: boolean; votesReceived: number; majorityRequired: number } {
  const majorityRequired = Math.floor(clusterSize / 2) + 1;
  let votesReceived = 1;
  for (const voter of voterLogs) {
    if (isCandidateLogUpToDate(candidateLog, voter)) votesReceived++;
  }
  return { quorumReached: votesReceived >= majorityRequired, votesReceived, majorityRequired };
}

export class RaftNode {
  public currentTerm = 0;
  public votedFor: number | null = null;
  public role: "follower" | "candidate" | "leader" = "follower";
  public log: LogEntry[] = [];
  public commitIndex = -1;
  public nextIndex = new Map<number, number>();
  public matchIndex = new Map<number, number>();
  public readonly nodeId: number;
  public readonly electionTimeoutMs: number;

  constructor(nodeId: number, seed = nodeId + 1) {
    this.nodeId = nodeId;
    const jitter = ((Math.imul(seed, 1664525) + 1013904223) >>> 0) % 151;
    this.electionTimeoutMs = 150 + jitter; // Randomized 150..300ms election timeout
  }

  lastLogMeta(): LogMeta {
    const lastIdx = this.log.length - 1;
    return { lastLogTerm: lastIdx >= 0 ? this.log[lastIdx].term : 0, lastLogIndex: lastIdx };
  }

  appendCommand(command: string): number {
    if (this.role !== "leader") throw new Error("Only leader can append client commands");
    this.log.push({ term: this.currentTerm, command });
    return this.log.length - 1;
  }

  requestVote(
    term: number,
    candidateId: number,
    lastLogIndex: number,
    lastLogTerm: number
  ): { term: number; voteGranted: boolean } {
    if (term > this.currentTerm) {
      this.currentTerm = term;
      this.role = "follower";
      this.votedFor = null;
    }
    if (term < this.currentTerm) {
      return { term: this.currentTerm, voteGranted: false };
    }
    const canVote = this.votedFor === null || this.votedFor === candidateId;
    const upToDate = isCandidateLogUpToDate(
      { lastLogTerm, lastLogIndex },
      this.lastLogMeta()
    );
    if (canVote && upToDate) {
      this.votedFor = candidateId;
      return { term: this.currentTerm, voteGranted: true };
    }
    return { term: this.currentTerm, voteGranted: false };
  }

  startElection(peers: readonly RaftNode[]): boolean {
    this.currentTerm += 1;
    this.role = "candidate";
    this.votedFor = this.nodeId;
    const meta = this.lastLogMeta();
    let votes = 1;
    const totalNodes = peers.length + 1;
    for (const peer of peers) {
      const reply = peer.requestVote(this.currentTerm, this.nodeId, meta.lastLogIndex, meta.lastLogTerm);
      if (reply.term > this.currentTerm) {
        this.currentTerm = reply.term;
        this.role = "follower";
        this.votedFor = null;
        return false;
      }
      if (reply.voteGranted) votes++;
    }
    if (votes >= Math.floor(totalNodes / 2) + 1) {
      this.role = "leader";
      this.nextIndex.clear();
      this.matchIndex.clear();
      for (const peer of peers) {
        this.nextIndex.set(peer.nodeId, this.log.length);
        this.matchIndex.set(peer.nodeId, -1);
      }
      return true;
    }
    return false;
  }

  replicateTo(peer: RaftNode): boolean {
    if (this.role !== "leader") return false;
    let nextIdx = this.nextIndex.get(peer.nodeId) ?? this.log.length;
    while (nextIdx >= 0) {
      const prevLogIndex = nextIdx - 1;
      const prevLogTerm = prevLogIndex >= 0 ? this.log[prevLogIndex].term : 0;
      const entries = this.log.slice(nextIdx);
      const reply = peer.appendEntries(
        this.currentTerm,
        this.nodeId,
        prevLogIndex,
        prevLogTerm,
        entries,
        this.commitIndex
      );
      if (reply.term > this.currentTerm) {
        this.currentTerm = reply.term;
        this.role = "follower";
        this.votedFor = null;
        return false;
      }
      if (reply.success) {
        const lastIdx = prevLogIndex + entries.length;
        this.nextIndex.set(peer.nodeId, lastIdx + 1);
        this.matchIndex.set(peer.nodeId, Math.max(this.matchIndex.get(peer.nodeId) ?? -1, lastIdx));
        return true;
      }
      nextIdx -= 1;
      this.nextIndex.set(peer.nodeId, Math.max(0, nextIdx));
    }
    return false;
  }

  advanceCommitIndex(clusterSize = this.matchIndex.size + 1): number {
    if (this.role !== "leader") return this.commitIndex;
    const majority = Math.floor(clusterSize / 2) + 1;
    for (let n = this.log.length - 1; n > this.commitIndex; n--) {
      // §5.4.2 (Figure 8): Leader only commits entries from its own currentTerm by counting replicas
      if (this.log[n].term !== this.currentTerm) continue;
      let count = 1; // Leader itself
      for (const mIdx of this.matchIndex.values()) {
        if (mIdx >= n) count++;
      }
      if (count >= majority) {
        this.commitIndex = n;
        break;
      }
    }
    return this.commitIndex;
  }

  appendEntries(
    term: number,
    _leaderId: number,
    prevLogIndex: number,
    prevLogTerm: number,
    entries: readonly LogEntry[],
    leaderCommit: number
  ): { term: number; success: boolean } {
    if (term < this.currentTerm) {
      return { term: this.currentTerm, success: false };
    }
    if (term > this.currentTerm) {
      this.votedFor = null;
    }
    this.currentTerm = term;
    this.role = "follower";

    if (prevLogIndex >= 0) {
      if (prevLogIndex >= this.log.length || this.log[prevLogIndex].term !== prevLogTerm) {
        return { term: this.currentTerm, success: false };
      }
    }
    let idx = prevLogIndex + 1;
    for (const entry of entries) {
      if (idx < this.log.length && this.log[idx].term !== entry.term) {
        this.log.length = idx;
      }
      if (idx >= this.log.length) {
        this.log.push({ ...entry });
      }
      idx++;
    }
    // Raft Figure 2 Receiver Step 5: min(leaderCommit, index of last new entry), monotonic forward only
    const indexOfLastNewEntry = prevLogIndex + entries.length;
    if (leaderCommit > this.commitIndex) {
      this.commitIndex = Math.max(
        this.commitIndex,
        Math.min(leaderCommit, indexOfLastNewEntry)
      );
    }
    return { term: this.currentTerm, success: true };
  }
}

Canonical Example

Input
5-node cluster (quorum = 3), Leader L1 replicates command x=42 at index 8 (term 2)
Output
Committed once L1 + 2 followers acknowledge index 8 in term 2
Explanation
Any future leader elected in term > 2 must overlap with at least one node in the 3-node quorum and therefore possesses index 8.

Implementation Notes

  • Persist currentTerm, votedFor, and log[] to stable storage before responding to any RPC.

Common Pitfalls

  • Committing log entries from previous terms by counting replicas directly instead of waiting for an entry from the leader's currentTerm (Figure 8 safety bug in the Raft paper).
  • Using identical election timeouts across nodes, causing split-vote livelocks.

Interactive Mechanism Walkthrough & State Debugger

Graph Frontier & Edge Relaxation

Follower -> Candidate -> Leader state transitions and majority quorum log commit.

Raft Distributed Consensus mechanism walkthrough step 1 of 5suvwxt
active vertexs
visited set V{ s }
frontier queue[ u ]
edges relaxed0 / 7
Step 1 of 5: Follower converts to Candidate if election timeout elapses without leader AppendEntries
graph-search

Notes

Tolerates up to f crash/network faults in a cluster of 2f + 1 nodes (unlike Byzantine fault tolerance which requires 3f + 1).

Sources (1)

What each reference supports for this entry.

Ongaro & Ousterhout (2014) — In Search of an Understandable Consensus Algorithm (Raft)

Paper

USENIX ATC 2014 paper specifying the Raft consensus protocol.

Open source

Explore Next

Good next jumps from the relationship graph, ranked for useful discovery.

Conceptual neighborMerkle Tree

Distributed stores pair Raft log replication with Merkle anti-entropy snapshots.

Related Algorithms

Tags

Distributed SystemsConsensusReplicationQuorumLeader Election