ЁЯПл The SchoolтА║ЁЯМР Distributed SystemsтА║ЁЯСС рдзрдбрд╛ 09 тАФ Consensus & Raft: рдПрдХ leader, рдПрдХ log
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯСС рдзрдбрд╛ 09 тАФ Consensus & Raft: рдПрдХ leader, рдПрдХ log

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 09 ┬╖ рдорд╛рдЧреЗ: lesson-08-cap-pacelc ┬╖ рдкреБрдвреЗ: lesson-10-locks-leases


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбреЗ 01тАУ08, рдЖрдгрд┐ рднрд╛рдЧ 3 рд╕реБрд░реВ рд╣реЛрддреЛ: рдПрдХрдордд. Consensus рдЕрдиреЗрдХ nodes рдирд╛ рдПрдХрд╛ value рд╡рд░ рдПрдХрдордд рдХрд░рд╛рдпрд▓рд╛ рд▓рд╛рд╡рддреЗ тАФ рдЗрдереЗ, рдПрдХ leader рдЖрдгрд┐ рдмрджрд▓рд╛рдВрдЪрд╛ рдПрдХ log тАФ рдХрд╛рд╣реА nodes рдмрдВрдж рдЕрд╕рд▓реНрдпрд╛ рдХрд┐рдВрд╡рд╛ рддреБрдЯрд▓реЗрд▓реНрдпрд╛ рдЕрд╕рд▓реНрдпрд╛ рддрд░реАрд╣реА. Raft рд╣реЗ terms, randomized election timeouts, RequestVote, majorities, AppendEntries рдЖрдгрд┐ commit index рд╡рд╛рдкрд░реВрди рдХрд░рддреЗ. dist/demo.py рдордзреАрд▓ raft() рдЖрдгрд┐ dist/sim.py рдордзреАрд▓ Raft.

тЪая╕П Lab рдордзреАрд▓ Raft рд╣реЗ рдПрдХ рд╕реЛрдкреЗ рдХреЗрд▓реЗрд▓реЗ model рдЖрд╣реЗ: рдмрд╣реБрдорддрд╛рдЪрд╛ рдирд┐рдпрдо рджрд╛рдЦрд╡рдгреНрдпрд╛рд╕рд╛рдареА рддреЗ рдорддреЗ рдЖрдгрд┐ рдкреНрд░рддреА рдореЛрдЬрддреЗ. рддреНрдпрд╛рдд timers рдирд╛рд╣реАрдд, рдкреНрд░рддреНрдпреЗрдХ node рдЪреЗ рд╡реЗрдЧрд│реЗ logs рдирд╛рд╣реАрдд, рдЖрдгрд┐ рдордд рджреЗрддрд╛рдирд╛ log рдЪреА рддрдкрд╛рд╕рдгреА рдирд╛рд╣реА. рдЦрд▒реНрдпрд╛ Raft рдордзреНрдпреЗ рд╣реЗ рддрд┐рдиреНрд╣реА рдЕрд╕рддрд╛рдд; рддреНрдпрд╛рдВрдЪреЗ рд╡рд░реНрдгрди рдЦрд╛рд▓реА рджрд┐рд▓реЗ рдЖрд╣реЗ.

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рдЖрддрд╛ рдкрд╛рдЪ рд╢рд╛рдЦрд╛ рдЖрд╣реЗрдд: рдкреБрдгреЗ, рдирд╛рд╢рд┐рдХ, рдирд╛рдЧрдкреВрд░, рдХреЛрд▓реНрд╣рд╛рдкреВрд░ рдЖрдгрд┐ рд╕рд╛рддрд╛рд░рд╛. рддреНрдпрд╛рдВрдирд╛ рдкреНрд░рддреНрдпреЗрдХ рдмрджрд▓рд╛рдЪрд╛ рдХреНрд░рдо рдард░рд╡рдгрд╛рд░реЗ рдПрдХ рдореБрдЦреНрдп office рд╣рд╡реЗ рдЖрд╣реЗ. ЁЯСС

рдирд┐рдпрдо: рдПрдЦрд╛рджреА рд╢рд╛рдЦрд╛ рдореБрдЦреНрдп office рддреЗрд╡реНрд╣рд╛рдЪ рдмрдирддреЗ рдЬреЗрд╡реНрд╣рд╛ рдмрд╣реБрдордд тАФ 5 рдкреИрдХреА 3 тАФ рддрд┐рд▓рд╛ рдордд рджреЗрддрд╛рдд. рдкреНрд░рддреНрдпреЗрдХ рдирд┐рд╡рдбрдгреВрдХ рдлреЗрд░реАрдд рдкреНрд░рддреНрдпреЗрдХ рд╢рд╛рдЦрд╛ рдПрдХрджрд╛рдЪ рдордд рджреЗрддреЗ. рдкреНрд░рддреНрдпреЗрдХ рдлреЗрд░реАрд▓рд╛ рдПрдХ рдХреНрд░рдорд╛рдВрдХ рдЕрд╕рддреЛ, рддреЛ рдореНрд╣рдгрдЬреЗ term.

рдирд╛рд╢рд┐рдХ term 1 рдордзреНрдпреЗ рдорддреЗ рдорд╛рдЧрддреЗ. рдкрд╛рдЪрд╣реА рдЬрдг рд╣реЛрдХрд╛рд░ рджреЗрддрд╛рдд. рдирд╛рд╢рд┐рдХ рдореБрдЦреНрдп office рдмрдирддреЗ. ЁЯОЙ

рдЖрддрд╛ рдирд╛рд╢рд┐рдХ "exam рдордВрдЧрд│рд╡рд╛рд░реА" рдЕрд╕реЗ рд▓рд┐рд╣рд┐рддреЗ рдЖрдгрд┐ рддреЗ рдЗрддрд░рд╛рдВрдирд╛ рдкрд╛рдард╡рддреЗ. 5 рдкреИрдХреА 3 рдЬрдгрд╛рдВрдиреА (рдирд╛рд╢рд┐рдХ рдЖрдгрд┐ рдЖрдгрдЦреА рджреЛрди) рддреЗ рд▓рд┐рд╣реВрди рдШреЗрддрд▓реЗ рдХреА рддреЗ committed рд╣реЛрддреЗ тАФ рдЕрдВрддрд┐рдо, рдХрдзреАрдЪ рдорд╛рдЧреЗ рди рдШреЗрддрд▓реЗ рдЬрд╛рдгрд╛рд░реЗ.

рдордЧ рд╡рд╛рджрд│ рд░рд╕реНрддрд╛ рддреЛрдбрддреЗ: рдПрдХрд╛ рдмрд╛рдЬреВрд▓рд╛ {рдирд╛рд╢рд┐рдХ, рдкреБрдгреЗ}, рджреБрд╕рд▒реНрдпрд╛ рдмрд╛рдЬреВрд▓рд╛ {рдирд╛рдЧрдкреВрд░, рдХреЛрд▓реНрд╣рд╛рдкреВрд░, рд╕рд╛рддрд╛рд░рд╛}.

5 рдкреИрдХреА 3 рдЪреЗ рдХреЛрдгрддреЗрд╣реА рджреЛрди рдЧрдЯ рдХрд┐рдорд╛рди рдПрдХ рд╢рд╛рдЦрд╛ рд╡рд╛рдЯреВрди рдШреЗрддрд╛рддрдЪ. рдореНрд╣рдгреВрди рдПрдХрд╛рдЪ term рдордзреНрдпреЗ рдХрдзреАрдЪ рджреЛрди рдореБрдЦреНрдп offices рдЕрд╕реВ рд╢рдХрдд рдирд╛рд╣реАрдд. рд▓рд╣рд╛рди рдмрд╛рдЬреВрд▓рд╛ рдерд╛рдВрдмрд╛рд╡реЗ рд▓рд╛рдЧрддреЗ. тП│

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    subgraph minority["2 of 5 тАФ cannot elect or commit"]
      na["ЁЯПл Nashik<br/>term 2: got 2/5"]
      pu["ЁЯПл Pune"]
    end
    subgraph majority["3 of 5 тАФ can elect"]
      ng["ЁЯСС Nagpur<br/>term 3: elected 3/5"]
      ko["ЁЯПл Kolhapur"]
      sa["ЁЯПл Satara"]
    end
    na -.-x|"тЬВя╕П road cut"| ng
    ng -->|"AppendEntries"| ko
    ng -->|"AppendEntries"| sa

ЁЯЧ║я╕П рд░реЗрдЦрд╛рдЯрд▓реЗрд▓реА рдЖрд╡реГрддреНрддреА + рдПрдХ lab: https://school-edh.pages.dev/distributed-systems/lesson-diagrams.html#l09

тЭУ рдХрд╛рдп

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг "leader рдХреЛрдг?" рдЖрдгрд┐ "рдмрджрд▓рд╛рдВрдЪрд╛ рдХреНрд░рдо рдХрд╛рдп?" рд╣реЗрдЪ рдкреНрд░рд╢реНрди failover (рдзрдбрд╛ 05), locks (рдзрдбрд╛ 10) рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ CP system (рдзрдбрд╛ 08) рдпрд╛рдВрдЪреНрдпрд╛рдорд╛рдЧреЗ рдЖрд╣реЗрдд. рдлрдХреНрдд heartbeat рдиреЗ рддреНрдпрд╛рдВрдЪреА рдЙрддреНрддрд░реЗ рджрд┐рд▓реА рддрд░ split brain рд╣реЛрддреЛ. рдмрд╣реБрдордд рдЖрдгрд┐ terms рдиреЗ рдЙрддреНрддрд░реЗ рджрд┐рд▓реА, рддрд░ рдЕрд▓реНрдкрд╕рдВрдЦреНрдп рдмрд╛рдЬреВ рдХрд╛рд╣реАрд╣реА рдХрд░реВ рд╢рдХрдд рдирд╛рд╣реА тАФ рддреНрдпрд╛рдЪреА рдХрд┐рдВрдордд рдореНрд╣рдгрдЬреЗ рддреА рдерд╛рдВрдмрддреЗ.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

dist/sim.py рдордзреАрд▓ Raft(nodes) term, leader рдЖрдгрд┐ log рдареЗрд╡рддреЗ. elect(candidate, alive) term рдордзреНрдпреЗ 1 рдорд┐рд│рд╡рддреЗ рдЖрдгрд┐ alive nodes рдирд╛ рдорддреЗ рдореНрд╣рдгреВрди рдореЛрдЬрддреЗ; рддреА len(votes) > len(nodes) // 2 рдЕрд╕реЗрд▓ рддрд░ рдЬрд┐рдВрдХрддреЗ тАФ рд╕рд░реНрд╡ nodes рдЪреЗ рдмрд╣реБрдордд. append(entry, reached) entry log рдордзреНрдпреЗ рдЬреЛрдбрддреЗ рдЖрдгрд┐ reached followers рдЖрдгрд┐ leader рдорд┐рд│реВрди рдмрд╣реБрдордд рд╣реЛрдд рдЕрд╕реЗрд▓ рддрд░ 'committed' рдкрд░рдд рдХрд░рддреЗ. рд╕реЛрдкреЗ рдХреЗрд▓реЗрд▓реЗ рднрд╛рдЧ: рдкреНрд░рддреНрдпреЗрдХ alive node рд╣реЛрдХрд╛рд░ рджреЗрддреЗ (log рдЪреА рддрдкрд╛рд╕рдгреА рдирд╛рд╣реА, рдкреНрд░рддреНрдпреЗрдХ term рдордзреНрдпреЗ рдПрдХ рдордд рдпрд╛рдЪреА рдиреЛрдВрдж рдирд╛рд╣реА), рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ node рдЪрд╛ рд╡реЗрдЧрд│рд╛ log рдирд╕реВрди рдПрдХрдЪ рд╕рд╛рдорд╛рдпрд┐рдХ log рдЖрд╣реЗ.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

python3 dist/demo.py raft
python3 - <<'EOF'
import sys; sys.path.insert(0, "dist"); from sim import Raft
for nodes, alive in ((3, 2), (4, 2), (4, 3), (5, 2), (5, 3), (6, 3), (7, 4)):
    r = Raft([f"b{i}" for i in range(nodes)])
    print(f"{nodes} nodes, {alive} reachable тЖТ " + r.elect("b0", alive={f"b{i}" for i in range(alive)}))
r = Raft(["pune", "nashik", "nagpur", "kolhapur"])
print("4 nodes split 2 | 2:", r.elect("pune", {"pune", "nashik"}), "┬╖", r.elect("nagpur", {"nagpur", "kolhapur"}))
EOF

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

raft рд╣реЗ print рдХрд░рддреЗ:

тФАтФА 5 branches run Raft; a leader needs a majority of ALL 5 (3 votes)
   term 1: nashik elected with 5/5 votes
   append 'exam on Tuesday', copied to pune + nagpur тЖТ committed тАФ 3 of 5 with the leader
   append 'trip on Friday', copied to pune only   тЖТ not committed (no majority) тАФ only 2 of 5
тФАтФА the road splits: {nashik, pune} | {nagpur, kolhapur, satara}
   term 2: nashik got 2/5 тАФ no majority, no leader
   term 3: nagpur elected with 3/5 votes
   the minority side cannot elect or commit тАФ so there are never two leaders in the same term

рддреБрдордЪрд╛ snippet рд╣реЗ print рдХрд░рддреЛ:

3 nodes, 2 reachable тЖТ term 1: b0 elected with 2/3 votes
4 nodes, 2 reachable тЖТ term 1: b0 got 2/4 тАФ no majority, no leader
4 nodes, 3 reachable тЖТ term 1: b0 elected with 3/4 votes
5 nodes, 2 reachable тЖТ term 1: b0 got 2/5 тАФ no majority, no leader
5 nodes, 3 reachable тЖТ term 1: b0 elected with 3/5 votes
6 nodes, 3 reachable тЖТ term 1: b0 got 3/6 тАФ no majority, no leader
7 nodes, 4 reachable тЖТ term 1: b0 elected with 4/7 votes
4 nodes split 2 | 2: term 1: pune got 2/4 тАФ no majority, no leader ┬╖ term 2: nagpur got 2/4 тАФ no majority, no leader

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

рдЕрд░реНрдзреЗ рдореНрд╣рдгрдЬреЗ рдмрд╣реБрдордд рдирд╡реНрд╣реЗ: 4 рдкреИрдХреА 2 рдЖрдгрд┐ 6 рдкреИрдХреА 3 рдХреЛрдгрд╛рд▓рд╛рдЪ рдирд┐рд╡рдбрдд рдирд╛рд╣реАрдд. рдореНрд╣рдгреВрди 2 | 2 рдЕрд╕рд╛ рдлреБрдЯрд▓реЗрд▓реНрдпрд╛ 4-node cluster рдордзреНрдпреЗ рдХреЛрдгрддреНрдпрд╛рд╣реА рдмрд╛рдЬреВрд▓рд╛ leader рдирд╕рддреЛ тАФ 3 nodes рдкреЗрдХреНрд╖рд╛рд╣реА рд╡рд╛рдИрдЯ, рдЬреЗ 2 рд╕рд╣ рддрд░реАрд╣реА рдирд┐рд╡рдб рдХрд░рддрд╛рдд. рдореНрд╣рдгреВрдирдЪ clusters рд╡рд┐рд╖рдо рд╕рдВрдЦреНрдпреЗрдЪреЗ рдЕрд╕рддрд╛рдд. рдЖрдгрд┐ "trip on Friday", рдЬреЗ рдлрдХреНрдд 5 рдкреИрдХреА 2 рдХрдбреЗ рд╣реЛрддреЗ, рддреЗ committed рдЭрд╛рд▓реЗ рдирд╛рд╣реА: рдирд╡реНрдпрд╛ leader рдХрдбреЗ рддреЗ рдирд╕реВ рд╢рдХрддреЗ, рдореНрд╣рдгреВрди рдХреЛрдгреАрд╣реА рддреЗ рдЕрдВрддрд┐рдо рдорд╛рдиреВ рдирдпреЗ.

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд

etcd (Kubernetes рдорд╛рдЧрдЪреЗ store) Raft рдЪрд╛рд▓рд╡рддреЗ. On a real account, рдкреНрд░рддреНрдпреЗрдХ member, leader рдЖрдгрд┐ рд╕рдзреНрдпрд╛рдЪрд╛ term рдкрд╛рд╣рд╛:

etcdctl --endpoints=http://pune:2379,http://nashik:2379,http://nagpur:2379 endpoint status -w table
# columns include: ENDPOINT ┬╖ IS LEADER ┬╖ RAFT TERM ┬╖ RAFT INDEX ┬╖ RAFT APPLIED INDEX
etcdctl --endpoints=http://pune:2379 member list -w table
etcdctl --endpoints=http://pune:2379 put exam-day Tuesday      # OK only once a majority has it
etcdctl --endpoints=http://pune:2379 move-leader 8211f1d0f64f3269   # hand leadership to another member (by ID)

ZooKeeper рдЕрд╕рд╛рдЪ рдПрдХ protocol рд╡рд╛рдкрд░рддреЛ, ZAB (ZooKeeper Atomic Broadcast): рдирд┐рд╡рдбрд▓реЗрд▓рд╛ leader, epochs (terms рд╕рд╛рд░рдЦреЗ), рдЖрдгрд┐ quorum (рдмрд╣реБрдордд) рдиреЗ рдорд╛рдиреНрдп рдХреЗрд▓реНрдпрд╛рд╡рд░ write committed рд╣реЛрддреЛ. zkCli.sh рдЖрдгрд┐ echo srvr | nc nashik 2181 рдкреНрд░рддреНрдпреЗрдХ server рдЪрд╛ mode рджрд╛рдЦрд╡рддрд╛рдд (leader рдХрд┐рдВрд╡рд╛ follower).

Kafka рдиреЗ ZooKeeper рдРрд╡рдЬреА KRaft рдЖрдгрд▓реЗ тАФ controller nodes рдЪрд╛ Raft-рдЖрдзрд╛рд░рд┐рдд quorum, рдЬреЛ cluster рдЪреНрдпрд╛ metadata рд╡рд░ рдПрдХрдордд рдХрд░рддреЛ. Consul, CockroachDB рдЖрдгрд┐ TiKV рд╕реБрджреНрдзрд╛ Raft рд╡рд╛рдкрд░рддрд╛рдд.

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: consensus clusters 3 рдХрд┐рдВрд╡рд╛ 5 members рд╕рд╣ рд╡реЗрдЧрд╡реЗрдЧрд│реНрдпрд╛ failure zones рдордзреНрдпреЗ рдЪрд╛рд▓рд╡рд╛, leader рдмрджрд▓рд╛рдВрд╡рд░ рдЖрдгрд┐ Raft index рдордзреНрдпреЗ рдорд╛рдЧреЗ рдкрдбрд▓реЗрд▓реНрдпрд╛ members рд╡рд░ alert рд▓рд╛рд╡рд╛, рдЖрдгрд┐ рддреНрдпрд╛рдВрдд рдлрдХреНрдд рдЫреЛрдЯрд╛ coordination data рдареЗрд╡рд╛ (leaders, config, locks) тАФ рдореЛрдареНрдпрд╛ рдкреНрд░рдорд╛рдгрд╛рддреАрд▓ application data рдирд╡реНрд╣реЗ.

тПня╕П рдкреБрдвреЗ

Consensus рдореБрд│реЗ рдПрдХ node lock рдзрд░реВ рд╢рдХрддреЗ. рдкрдг lock рдзрд░рдгрд╛рд░реА node рдерд╛рдВрдмреВ рд╢рдХрддреЗ рдЖрдгрд┐ рддрд┐рд▓рд╛ рддреЗ рдХрд│рддрд╣реА рдирд╛рд╣реА. рдкреБрдвреЗ: leases рдЖрдгрд┐ fencing tokens.

git checkout lesson-10-locks-leases

ЁЯСС Lesson 09 тАФ Consensus & Raft: one leader, one log

ЁЯУН You are here: Lesson 09 of 12 ┬╖ Previous: lesson-08-cap-pacelc ┬╖ Next: lesson-10-locks-leases


ЁЯУж What's in this branch

Lessons 01тАУ08, plus Part 3 begins: agreement. Consensus gets several nodes to agree on one value тАФ here, one leader and one log of changes тАФ even when some are down or cut off. Raft does it with terms, randomized election timeouts, RequestVote, majorities, AppendEntries and a commit index. raft() in dist/demo.py and Raft in dist/sim.py.

тЪая╕П The lab's Raft is a simplified model: it counts votes and copies to show the majority rule. It has no timers, no per-node logs, and no log check on votes. Real Raft has all three; they are described below.

ЁЯзТ Explain like I'm 5

Now there are five branches: Pune, Nashik, Nagpur, Kolhapur and Satara. They want one head office that decides the order of every change. ЁЯСС

The rule: a branch becomes head office only if a majority тАФ 3 of the 5 тАФ vote for it. Each branch votes once per election round. Each round has a number, the term.

Nashik asks for votes in term 1. All five agree. Nashik is head office. ЁЯОЙ

Now Nashik writes "exam on Tuesday" and sends it to the others. When 3 of 5 (Nashik plus two more) have written it down, it is committed тАФ final, never undone.

Then the storm cuts the road: {Nashik, Pune} on one side, {Nagpur, Kolhapur, Satara} on the other.

Two groups of 3 out of 5 must share at least one branch. So there can never be two head offices in the same term. The small side must wait. тП│

ЁЯЧ║я╕П Diagram

flowchart LR
    subgraph minority["2 of 5 тАФ cannot elect or commit"]
      na["ЁЯПл Nashik<br/>term 2: got 2/5"]
      pu["ЁЯПл Pune"]
    end
    subgraph majority["3 of 5 тАФ can elect"]
      ng["ЁЯСС Nagpur<br/>term 3: elected 3/5"]
      ko["ЁЯПл Kolhapur"]
      sa["ЁЯПл Satara"]
    end
    na -.-x|"тЬВя╕П road cut"| ng
    ng -->|"AppendEntries"| ko
    ng -->|"AppendEntries"| sa

ЁЯЧ║я╕П Drawn version + a lab: https://school-edh.pages.dev/distributed-systems/lesson-diagrams.html#l09

тЭУ What

ЁЯдФ Why

Because "who is the leader?" and "what is the order of changes?" are the questions behind failover (lesson 05), locks (lesson 10) and every CP system (lesson 08). Answer them with only a heartbeat, and you get split brain. Answer them with majorities and terms, and the minority side cannot act тАФ the price is that it waits.

ЁЯФз How (in this repo)

Raft(nodes) in dist/sim.py keeps term, leader and log. elect(candidate, alive) adds 1 to the term and counts the alive nodes as votes; it wins with len(votes) > len(nodes) // 2 тАФ a majority of all nodes. append(entry, reached) adds the entry to the log and returns 'committed' when the followers reached plus the leader make a majority. Simplifications: every alive node votes yes (no log check, no one-vote-per-term record), and there is one shared log, not one per node.

ЁЯзк Try it

python3 dist/demo.py raft
python3 - <<'EOF'
import sys; sys.path.insert(0, "dist"); from sim import Raft
for nodes, alive in ((3, 2), (4, 2), (4, 3), (5, 2), (5, 3), (6, 3), (7, 4)):
    r = Raft([f"b{i}" for i in range(nodes)])
    print(f"{nodes} nodes, {alive} reachable тЖТ " + r.elect("b0", alive={f"b{i}" for i in range(alive)}))
r = Raft(["pune", "nashik", "nagpur", "kolhapur"])
print("4 nodes split 2 | 2:", r.elect("pune", {"pune", "nashik"}), "┬╖", r.elect("nagpur", {"nagpur", "kolhapur"}))
EOF

тЬЕ Verify тАФ what you should see

raft prints:

тФАтФА 5 branches run Raft; a leader needs a majority of ALL 5 (3 votes)
   term 1: nashik elected with 5/5 votes
   append 'exam on Tuesday', copied to pune + nagpur тЖТ committed тАФ 3 of 5 with the leader
   append 'trip on Friday', copied to pune only   тЖТ not committed (no majority) тАФ only 2 of 5
тФАтФА the road splits: {nashik, pune} | {nagpur, kolhapur, satara}
   term 2: nashik got 2/5 тАФ no majority, no leader
   term 3: nagpur elected with 3/5 votes
   the minority side cannot elect or commit тАФ so there are never two leaders in the same term

Your snippet prints:

3 nodes, 2 reachable тЖТ term 1: b0 elected with 2/3 votes
4 nodes, 2 reachable тЖТ term 1: b0 got 2/4 тАФ no majority, no leader
4 nodes, 3 reachable тЖТ term 1: b0 elected with 3/4 votes
5 nodes, 2 reachable тЖТ term 1: b0 got 2/5 тАФ no majority, no leader
5 nodes, 3 reachable тЖТ term 1: b0 elected with 3/5 votes
6 nodes, 3 reachable тЖТ term 1: b0 got 3/6 тАФ no majority, no leader
7 nodes, 4 reachable тЖТ term 1: b0 elected with 4/7 votes
4 nodes split 2 | 2: term 1: pune got 2/4 тАФ no majority, no leader ┬╖ term 2: nagpur got 2/4 тАФ no majority, no leader

ЁЯПБ What you just proved

Half is not a majority: 2 of 4 and 3 of 6 elect nobody. So a 4-node cluster split 2 | 2 has no leader on either side тАФ worse than 3 nodes, which still elect with 2. That is why clusters use an odd size. And "trip on Friday", held by only 2 of 5, was not committed: a new leader may not have it, so no one may treat it as final.

тЪая╕П Common mistakes

ЁЯПн In production

etcd (the store behind Kubernetes) runs Raft. On a real account, see each member, the leader and the current term:

etcdctl --endpoints=http://pune:2379,http://nashik:2379,http://nagpur:2379 endpoint status -w table
# columns include: ENDPOINT ┬╖ IS LEADER ┬╖ RAFT TERM ┬╖ RAFT INDEX ┬╖ RAFT APPLIED INDEX
etcdctl --endpoints=http://pune:2379 member list -w table
etcdctl --endpoints=http://pune:2379 put exam-day Tuesday      # OK only once a majority has it
etcdctl --endpoints=http://pune:2379 move-leader 8211f1d0f64f3269   # hand leadership to another member (by ID)

ZooKeeper uses a similar protocol, ZAB (ZooKeeper Atomic Broadcast): an elected leader, epochs (like terms), and a write is committed once a quorum (majority) acknowledges it. zkCli.sh and echo srvr | nc nashik 2181 show each server's mode (leader or follower).

Kafka replaced ZooKeeper with KRaft тАФ a Raft-based quorum of controller nodes that agree on the cluster's metadata. Consul, CockroachDB and TiKV use Raft too.

ЁЯПн Why this matters in production: run consensus clusters with 3 or 5 members in separate failure zones, alert on leader changes and on members behind in the Raft index, and keep only small coordination data in them (leaders, config, locks) тАФ not bulk application data.

тПня╕П Next

With consensus, one node can hold a lock. But a lock holder can pause and not know it. Next: leases and fencing tokens.

git checkout lesson-10-locks-leases
тЖР Previouscap pacelcNext тЖТlocks leases

This page is the lesson's README from the lesson-09-raft branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.