Skip to content

leader failure and re-election

Raft leader election in a five-node cluster.

Edit in the playground

The source — 04-diagram-types/03-raft-election.dgm
%% Raft leader election in a five-node cluster.
%% ---
%% The point of this example is that the cluster's state is legible at any
%% scrub position. Who is leader, what term we are in and how many votes a
%% candidate has collected are not things you can infer from a particle that
%% has already gone past — so they are `set` and `gauge` state, which the
%% compiler keeps open until something replaces it.
flowchart TB
  subgraph cluster[Raft Cluster]
    n1[n1]
    n2[n2]
    n3[n3]
    n4[n4]
    n5[n5]
  end

  n1 --> n2
  n1 --> n3
  n1 --> n4
  n1 --> n5

  n3 --> n1
  n3 --> n2
  n3 --> n4
  n3 --> n5

scenario "leader failure and re-election" { speed: 1.0 }

  step steady "n1 is leader and heartbeats the followers" {
    desc: "A Raft leader holds its position by talking. Every follower that hears an AppendEntries within its election timeout stays a follower."
    set n1 { badge: "leader", state: leader, color: "#16a34a" }
    gauge n1 { label: "term", value: 1 }
    flow n1 -> n2 { label: "AppendEntries", dur: 500ms }
    flow n1 -> n3 { dur: 500ms }
    flow n1 -> n4 { dur: 500ms }
    flow n1 -> n5 { dur: 500ms }
  }

  step down "n1 stops responding" {
    desc: "Nothing announces a leader failure. The followers only observe an absence, which is why the whole protocol is built on a timeout rather than on a notification."
    unset n1
    dim n1
    flow n1 -> n3 { label: "heartbeat lost", dur: 600ms, status: fail }
  }

  step timeout "n3's election timer fires first" {
    desc: "Election timeouts are randomised precisely so that one node usually reaches zero first. n3 increments the term, votes for itself, and asks the others to agree."
    set n3 { badge: "candidate", state: candidate, color: "#d97706" }
    gauge n3 { label: "term", value: 2 }
    gauge n3 { label: "votes", value: "1 / 5" }
    dim n1
    flow n3 -> n2 { label: "RequestVote", dur: 500ms }
    flow n3 -> n4 { dur: 500ms }
  }

  step votes "A majority answers" {
    desc: "Each follower grants at most one vote per term, so two candidates in the same term cannot both win. Three of five is a majority and that is enough."
    gauge n3 { label: "votes", value: "3 / 5" }
    dim n1
    flow n2 -> n3 { label: "granted", dur: 450ms, style: response }
    flow n4 -> n3 { label: "granted", dur: 450ms, style: response }
  }

  step elected "n3 becomes leader for term 2" {
    desc: "The badge moves because the role moved. n1's leadership was retired the moment it went silent, and nothing in the diagram still claims otherwise."
    set n3 { badge: "leader", state: leader, color: "#16a34a" }
    gauge n3 { label: "votes", value: "" }
    dim n1
    pulse n3
  }

  step replicate "Log replication resumes under the new leader" {
    desc: "From here the cluster is back to the steady state it started in, one term later and one node down. Scrub back and forth: the badges and gauges say what is true at that moment, not what happened to animate last."
    dim n1
    flow n3 -> n2 { label: "AppendEntries", dur: 500ms }
    flow n3 -> n4 { dur: 500ms }
    flow n3 -> n5 { dur: 500ms }
  }

tcp connection
one question, four turns