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.
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=\lfloorN/2\rfloor+1,\quadN=2f+1
Q = \lfloor N / 2 \rfloor + 1,\quad N = 2f + 1
Any two majority quorums intersect in at least one server.
Pseudocode
1.Follower converts to Candidate if election timeout elapses without leader AppendEntries
2.Candidate increments currentTerm, votes for self, and broadcasts RequestVote(term, lastLogIndex, lastLogTerm)
3.Voter grants vote iff candidateTerm >= currentTerm, not yet voted in term, and candidate log is at least as up-to-date
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.
raftConsensus.ts
/**
* 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.
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.