Unclean Leader Election

Concepts covered: In-sync replicas

A clean election picks the new leader from the in-sync replicas, so the new leader holds every record the old one had committed. That rule has a gap. The ISR can shrink to the leader alone when every follower falls behind, and if that leader then dies, no replica is in sync with it. The controller has 2 options: leave the partition without a leader until the dead broker returns, or make a replica that is missing records the leader. The setting that picks between them is unclean.leader.election.enable.

A partition with one replica left in sync

Take partition 3 of orders, replicated on brokers 0, 1 and 2 with broker 0 leading. Both followers sit on a rack whose switch starts dropping packets, their fetches stall, and after replica.lag.time.max.ms the leader removes them from the ISR. Writes keep landing: min.insync.replicas is 1, the ISR is {0}, and the high watermark advances with every record. The log end offsets drift apart.

BrokerRoleLog end offsetIn the ISR
0leader100yes
1follower61no
2follower58no

At offset 100, broker 0 loses power and goes down. Its disk still holds offsets 0 to 99, but no one can read them until the machine boots again. The controller sees a dead leader and an ISR with nobody else in it.

Off: wait for the leader

With the setting at its default of false, the controller leaves partition 3 with no leader. Writes to it fail, readers on it get nothing, and the other partitions of orders carry on. When broker 0 comes back with its log intact, it resumes as leader at offset 100, the followers catch up, and every acknowledged record is still there. The price is that this partition stays offline for as long as broker 0 is down.

On: promote a stale replica

With the setting at true, the controller makes a replica from outside the ISR the leader. It never sees the replicas' log end offsets, so it does not look for the freshest one. It takes the first live broker in the partition's replica list, here [0, 1, 2], which is broker 1. Broker 1 leads from its own log end, offset 61, and the ISR becomes {1}. Records 61 through 99, all acknowledged, now exist only on broker 0's powered-off disk. Had the list been [0, 2, 1], broker 2 would lead from offset 58 and records 58 to 99 would be lost, 3 more than here, while broker 1 sat holding them. New writes arrive at once and the leader assigns them offsets starting at 61. Offset 80 now names a record written after the failure, and any reader that already consumed the old record 80 holds a value the cluster no longer has.

The last in-sync replica dies
  • 3 brokers
  • orders: 1 partition, replication factor 3
  • Election off (default): No in-sync replica remains, so the partition has no leader and the sends after the kill fail. Broker 0 then returns with offsets 0 to 6 intact, leads again, and the next send lands at offset 7.
  • Election on: The first live broker in the replica list, broker 1, becomes leader at its own log end; records 4 to 6 are gone and the new sends take their offsets.

Both followers stop fetching and leave the ISR, so records 4 to 6 reach broker 0 alone. When broker 0 dies, the switch decides whether the partition waits for broker 0 to return or lets a stalled follower lead from its shorter log.

The old leader truncates on return

Broker 0 restarts holding offsets 0 to 99 and finds broker 1 leading with a newer leader epoch. It asks the leader where their logs diverge, learns that the leader's epoch began at offset 61, truncates its own log to offset 61, and fetches the new records from there. The 39 records it had are gone from the cluster for good. A reader whose position sat inside the lost range meets one of 2 outcomes, depending on where that position falls against the new leader's log end. A reader at offset 90 while the log ends at 65 asks for an offset that does not exist, gets an offset-out-of-range error, and must restart from the log's start or its end by whatever rule it was configured with. A reader at offset 63 gets no error at all: it reads on, and records 63 to 65 are ones written after the failure, different from the records it would have read there before. Nothing warns a reader that offsets 61 to 99 now hold different records; the only trace is a jump in the leader epoch, which the broker writes to its own log file.

MomentBroker 0 logBroker 1 logReadable range
before the crash0 to 990 to 600 to 99
after the unclean electionoffline0 to 600 to 60
after 5 new writesoffline0 to 650 to 65
broker 0 rejoins0 to 65, truncated0 to 650 to 65
Election off (default)
  • The partition stays offline until a replica with the full log returns
  • Every acknowledged record survives
  • Writers and readers of that partition wait
Election on
  • A new leader is chosen within seconds
  • Records only the dead leader held are dropped
  • Offsets past the divergence point are reassigned to new records

Choosing per topic

The setting is a topic config and overrides the broker default. A topic of page-view counts can run with it on, since a gap of 40 records costs a dashboard a blip. A topic of payments runs with it off and accepts the outage. You can also leave it off everywhere and trigger a single unclean election by hand when the wait has gone on too long: kafka-leader-election.sh --bootstrap-server b1:9092 --election-type UNCLEAN --topic orders --partition 3. That choice is yours at the moment, rather than the controller's by rule.

unclean.leader.election.enable=true

About this lesson

This section is part of the Replication: Advanced lesson on DataDriven, the home of data engineering. Lessons cover SQL, Python, Spark, data modeling and pipeline architecture, and each section pairs its explanation with worked examples and practice you run as you go.