ML
Apache Kafka

Kafka Unclean Leader Election: The Silent Data Loss Trap Behind High Availability

Enabling unclean.leader.election guarantees partition uptime at the expense of silent log truncation and message drops. How ISR fencing and min.insync.replicas protect financial state.

September 05, 202611 min readKafkaArchitectureDistributed SystemsHigh Availability

In distributed data platforms, system design often forces an explicit choice between availability and consistency. In Apache Kafka, that balance is governed by the partition leader election protocol and the setting unclean.leader.election.enable. While keeping partition leaders online sounds universally desirable to avoid downtime, allowing unclean leader elections can silently destroy committed messages and violate ordering guarantees across downstream databases.

1. The In-Sync Replicas (ISR) Contract

Kafka partitions replicate records across multiple brokers. At any given moment, the partition replicas are categorized into two groups:

  • In-Sync Replicas (ISR): Brokers that are actively fetching messages from the leader and whose replication lag is strictly within replica.lag.time.max.ms.
  • Out-of-Sync Replicas (OSR): Brokers that have stalled, suffered GC pauses, or fallen behind the leader log end offset (LEO).

When a producer writes with acks=all, the leader only acknowledges the write after all current ISR members have written the record to their local write-ahead log. The point up to which all ISR members agree is designated as the High Watermark (HW). Consumers are only permitted to read records up to the High Watermark.

2. What Happens During an Unclean Leader Election?

Consider a partition with three replicas: Broker A (Leader, LEO=100), Broker B (Follower, LEO=100), and Broker C (Follower, slow network, LEO=60). The ISR list is [A, B].

Broker A (Leader):  [ ... offsets 0 to 100 ... ]  (ISR)
Broker B (Follower):[ ... offsets 0 to 100 ... ]  (ISR)
Broker C (Follower):[ ... offsets 0 to 60  ... ]  (Out of sync)

--- Scenario: Power loss knocks out Rack 1 (Brokers A and B) ---

With Brokers A and B offline, the partition has zero available ISR members. What should Kafka do?

Behavior A: Clean Election Only (unclean.leader.election.enable = false)

The cluster refuses to elect Broker C. The partition becomes unavailable for both reads and writes. Producers receive NOT_ENOUGH_REPLICAS or LEADER_NOT_AVAILABLE errors. System administrators are alerted, but no data is lost. As soon as Broker A or B boots back up, the partition resumes cleanly at offset 100.

Behavior B: Unclean Election Enabled (unclean.leader.election.enable = true)

The controller elects Broker C as the new partition leader because availability is prioritized over correctness. Now disaster strikes in two steps:

  1. Immediate Message Drop: Broker C only possesses records up to offset 60. Records 61 through 100, which were acknowledged to producers and potentially consumed by downstream payment processors, are vanished from the active leader view. New incoming messages are written starting at offset 61 on Broker C.
  2. Log Truncation upon Reconnect: When Broker A recovers and rejoins the cluster as a follower, it queries Broker C for its Leader Epoch. Recognizing that Broker C is the authoritative leader for epoch N+1, Broker A is forced to truncate its local log down to offset 60 to match Broker C, permanently erasing offsets 61-100!

3. Downstream Consequences: Offset Inversion and Phantom Duplicates

The damage caused by log truncation extends far beyond the Kafka broker storage. Modern event-driven pipelines rely on monotonic offsets for deduplication, state tracking, and exactly-once processing:

  • Consumer State Desynchronization: If a consumer committed offset 95 before the outage, and the partition reopens under Broker C at offset 61, the consumer will see no new messages until Broker C writes 35 new records. It completely misses new events produced at offsets 61-94.
  • Database Drift: If an external datastore was already updated using records from the original offsets 61-100, and new unrelated records are subsequently assigned those same offset numbers under the new leader, deduplication tables keying on (topic, partition, offset) will reject legitimate transactions as duplicates.

4. Production Configurations for Maximum Data Integrity

For transactional systems, payment ledgers, and audit logs, clean elections and strict ISR fencing are non-negotiable. The recommended cluster and topic configuration profile:

# 1. Disable unclean leader election cluster-wide and per topic
unclean.leader.election.enable=false

# 2. Require all in-sync replicas to acknowledge
# On producer side:
acks=all

# 3. Minimum replicas that must write before an ack is returned
# For replication factor 3, set min.insync.replicas to 2:
min.insync.replicas=2

With min.insync.replicas=2 and acks=all, if two out of three brokers fail, the sole surviving broker will reject new producer writes with NotEnoughReplicasException instead of silently accepting writes that cannot be replicated. This preserves total consistency across failure domains.

Summary

Unclean leader election is a legacy trade-off suitable only for telemetry, ephemeral metrics, or non-critical tracking where losing minutes of events is preferable to a temporary pipeline block. For core enterprise backends, keeping unclean.leader.election.enable=false combined with min.insync.replicas=2 guarantees that committed data remains immutable and dependable.

SharePostLinkedIn

Reader Discussion

8 replies// weighed in

TopNewestAuthor
Add to the thread
Disagree, agree harder, or share your own experience…
Email instead →markdown okbe kind
  1. Highlighted by author
    Tuấn Phạm🇻🇳 HCMC· Staff Engineer · Tiki Data PlatformStory

    min.insync.replicas=2 với acks=all là combo chuẩn. Bọn em từng để mặc định min.insync.replicas=1, một broker rolling restart là mất 4 message — xui là đúng cái event payment confirm. Ngồi viết postmortem từ 2h sáng đến 7h.

    Sep 06, 2026·1 day later
    • ML
      Minh LeAuthor

      Đúng cái khoảnh khắc realize default = 1 thì đã muộn. Cảm ơn bro share — mình sẽ thêm warning box vô post.

      Sep 06, 2026
  2. Derek Okonkwo· Principal Engineer · FintechAgrees

    The Kafka→anything-else caveat is the single most important paragraph in this post. I cannot count how many "exactly once" pipelines I've reviewed that write to Postgres without an outbox and the team still calls it EOS. It's at-least-once with extra steps and a worse mental model.

    Sep 06, 2026·1 day later
  3. Sven Bergström· Senior SWEStory

    fresh transactional.id on each pod restart is THE silent killer. We had it for ~14 months. Realised when a zombie producer wrote 80k duplicates after a node went catatonic and came back.

    Sep 10, 2026·5 days later
  4. Quốc Anh· Backend Lead · FinhayAgrees

    Đoạn segment files giải thích quá ngắn gọn. Bọn em hay quên log.segment.bytes vs log.retention.bytes là 2 thằng khác nhau, bị retention không kick in là vì segment chưa rolled — đúng cái rule of thumb cuối bài.

    Sep 07, 2026·2 days later
  5. Amelia Brooks· Distributed SystemsPushback

    tiny nit but acks=0 is not literally fire-and-forget at the protocol layer — the producer still writes to its socket buffer. The 'forget' is the broker side. Pedantic but bites people in metrics dashboards.

    Sep 14, 2026·1 week later
  6. Isabella Costa· Junior EngineerKind words

    saved this. sharing at standup tomorrow — we've had exactly this problem for 2 sprints and nobody on the team had framed it this way 🙏

    Sep 07, 2026·2 days later
  7. Kenta Yamada· Tech LeadAsks

    would love a war-story follow-up. principles are clear; the actual debugging session is where the interesting stuff lives. there's a real shortage of "here's the dashboard, here's the thread we pulled, here's where we got stuck for 90 mins" content.

    Sep 09, 2026·4 days later

Worked on something similar? Email ducminhldm@gmail.com — I read every one. The good ones become future posts.

Comments seeded · live discussion via email