Skip to content

Leader Election

Selecting a single coordinator in distributed systems

In distributed systems, we often need a single coordinator to:

  • Make decisions: Which node handles a request?
  • Manage resources: Coordinate access to shared resources
  • Maintain consistency: Ensure all nodes agree on state
  • Handle failures: Detect and recover from node failures

The Challenge: How do multiple nodes agree on who the leader is? How do we handle leader failures? How do we prevent split-brain (multiple leaders)?

Leader election problem: nodes ask who is leader, needing a single coordinator without split-brain, solved by Bully, Raft or ZooKeeper

Leader election is the process of selecting a single node as the coordinator (leader) in a distributed system. The leader handles coordination tasks, while other nodes (followers) follow the leader’s decisions.

  1. Safety: Only one leader at a time (no split-brain)
  2. Liveness: Eventually a leader exists (even after failures)
  3. Fault Tolerance: Handles node failures gracefully
  4. Performance: Fast election, minimal overhead
  5. Uniqueness: No conflicting leaders

Think of leader election like electing a class president:

  • Multiple candidates (nodes) can run
  • Election process determines winner (leader)
  • If president is absent (leader fails), new election held
  • Only one president at a time (safety)
  • Eventually someone is elected (liveness)

The Bully algorithm elects a leader based on node ID. The node with the highest ID wins.

Bully algorithm election: node detects leader failure, messages higher IDs, and becomes leader if none respond, so the highest ID wins

How It Works:

  1. When a node detects leader failure, it initiates election
  2. Node sends election message to all nodes with higher IDs
  3. If no response from higher nodes, node becomes leader
  4. If response received, wait for leader announcement
  5. Leader announces itself to all nodes

Advantages:

  • Simple to understand and implement
  • Fast election (O(n) messages)
  • Deterministic (highest ID always wins)

Disadvantages:

  • Can have multiple elections if multiple nodes detect failure simultaneously
  • Requires all nodes to know all other nodes
  • Not fault-tolerant if highest ID node is unstable

Raft is a consensus algorithm that provides leader election and log replication. It’s more complex than Bully but provides stronger guarantees.

Nodes in Raft can be in one of three states:

  • Leader: Handles all client requests, replicates log to followers
  • Follower: Receives log entries from leader, votes in elections
  • Candidate: Campaigning to become leader
Raft state transitions: follower times out to candidate, candidate wins majority votes to become leader, higher term reverts to follower

How Raft Election Works:

  1. Follower doesn’t receive heartbeat from leader (timeout)
  2. Follower becomes candidate and increments term
  3. Candidate requests votes from all nodes
  4. If candidate receives majority votes, becomes leader
  5. Leader sends heartbeats to prevent new elections

Key Features:

  • Majority voting: Prevents split-brain
  • Terms: Each election has a term number (monotonically increasing)
  • Log replication: Leader replicates log entries to followers
  • Safety: Only one leader per term
💡 Tip: Click dropdown to switch between languages
"leader_election.py
import threading
import time
import random
from enum import Enum
from typing import List, Optional, Dict
from dataclasses import dataclass
class NodeState(Enum):
FOLLOWER = "follower"
CANDIDATE = "candidate"
LEADER = "leader"
@dataclass
class VoteRequest:
candidate_id: int
term: int
@dataclass
class VoteResponse:
voter_id: int
term: int
granted: bool
class RaftNode:
"""Raft node with leader election"""
def __init__(self, node_id: int, all_nodes: List[int]):
self.node_id = node_id
self.all_nodes = all_nodes
self.state = NodeState.FOLLOWER
self.current_term = 0
self.voted_for: Optional[int] = None
self.leader_id: Optional[int] = None
self.election_timeout = random.uniform(1.5, 3.0) # Random timeout
self.last_heartbeat = time.time()
self.votes_received = 0
self.lock = threading.Lock()
# Start election timeout thread
threading.Thread(target=self._election_timeout_loop, daemon=True).start()
def _election_timeout_loop(self):
"""Check for election timeout"""
while True:
time.sleep(0.1)
with self.lock:
if self.state == NodeState.FOLLOWER:
if time.time() - self.last_heartbeat > self.election_timeout:
# Start election
self._start_election()
def _start_election(self):
"""Start leader election"""
print(f"Node {self.node_id}: Starting election for term {self.current_term + 1}")
self.state = NodeState.CANDIDATE
self.current_term += 1
self.voted_for = self.node_id
self.votes_received = 1 # Vote for self
# Request votes from all other nodes
for node_id in self.all_nodes:
if node_id != self.node_id:
# In real implementation, send RPC to other nodes
# For demo, simulate vote response
self._request_vote(node_id)
def _request_vote(self, node_id: int):
"""Request vote from another node (simplified)"""
# In real implementation, this would be an RPC call
# For demo, simulate: higher ID nodes grant vote
if node_id > self.node_id:
# Simulate vote granted
self._receive_vote(node_id, True)
def _receive_vote(self, voter_id: int, granted: bool):
"""Receive vote response"""
with self.lock:
if granted:
self.votes_received += 1
majority = len(self.all_nodes) // 2 + 1
if self.votes_received >= majority:
# Become leader
self._become_leader()
def _become_leader(self):
"""Become leader"""
print(f"Node {self.node_id}: Becoming leader for term {self.current_term}")
self.state = NodeState.LEADER
self.leader_id = self.node_id
# Start sending heartbeats
threading.Thread(target=self._send_heartbeats, daemon=True).start()
def _send_heartbeats(self):
"""Send heartbeats to followers"""
while self.state == NodeState.LEADER:
time.sleep(0.5) # Heartbeat interval
# In real implementation, send heartbeat RPCs to all followers
print(f"Node {self.node_id}: Sending heartbeats (term {self.current_term})")
def receive_heartbeat(self, leader_id: int, term: int):
"""Receive heartbeat from leader"""
with self.lock:
if term >= self.current_term:
self.current_term = term
self.leader_id = leader_id
self.state = NodeState.FOLLOWER
self.last_heartbeat = time.time()
self.voted_for = None
def request_vote(self, candidate_id: int, term: int) -> bool:
"""Handle vote request from candidate"""
with self.lock:
if term > self.current_term:
self.current_term = term
self.voted_for = candidate_id
self.state = NodeState.FOLLOWER
return True
elif term == self.current_term and self.voted_for is None:
self.voted_for = candidate_id
return True
return False
# Usage (simplified demo)
nodes = [1, 2, 3]
raft_nodes = [RaftNode(node_id, nodes) for node_id in nodes]
# Simulate: Node 1 becomes leader
time.sleep(2)
print(f"Node 1 state: {raft_nodes[0].state}")

Apache ZooKeeper provides built-in support for leader election using ephemeral sequential nodes:

  1. All nodes create ephemeral sequential nodes under /election
  2. Node with smallest sequence number becomes leader
  3. If leader fails, ephemeral node deleted, next node becomes leader
  4. Nodes watch the node with sequence number one less than theirs

Advantages:

  • Handled by ZooKeeper (no custom implementation needed)
  • Automatic failure detection
  • No split-brain (ZooKeeper provides consistency)

Problem: Network partition causes multiple leaders.

Solution: Require majority vote (quorum). Only partition with majority can elect leader.

Problem: Multiple nodes start election simultaneously.

Solution: Use random election timeout. Reduces probability of simultaneous elections.

Problem: How to detect leader failure quickly?

Solution: Use heartbeat mechanism. If no heartbeat received within timeout, assume leader failed.

Leader handles writes, replicates to followers. Ensures consistency and handles failures.

Leader coordinates distributed operations, manages shared state.

Leader makes decisions, followers replicate state. Provides fault tolerance.

Advantages:

  • Provides single coordinator
  • Handles failures gracefully
  • Prevents split-brain (with majority voting)
  • Enables coordination

Disadvantages:

  • Adds complexity
  • Leader can become bottleneck
  • Network partitions can cause issues
  • Requires majority for election

Single Coordinator

Leader election ensures only one node acts as coordinator. Provides mutual exclusion for coordination tasks.

Majority Voting

Require majority votes to prevent split-brain. Only partition with majority can elect leader.

Fault Tolerance

Leader failures trigger new elections. System eventually elects new leader. Handles node failures gracefully.

Heartbeat Mechanism

Leader sends heartbeats to prevent elections. Followers detect leader failure via timeout. Triggers new election.