Skip to main content
Back to consensus paxos raft
distributed systems · Deep dive

Kafka — from ZooKeeper to KRaft

9 min read
Fully authored

Kafka 2.8 (2021) introduced KRaft — Kafka's own embedded Raft. Kafka 4.0 (2025) removed ZooKeeper entirely. Learn how a message queue got its own consensus engine.

For its first 10 years, Apache Kafka needed ZooKeeper. Broker registration, controller election, topic metadata, ACLs, quotas, partition assignments — every one of those pieces of cluster state lived in a ZooKeeper ensemble running alongside the Kafka brokers. If you ran Kafka in production, you ran ZooKeeper too. Two clusters, two failure modes, two upgrade paths, twice the on-call pain.

In April 2021, Kafka 2.8 shipped KRaft mode (KIP-500) — Kafka's own embedded Raft implementation that finally let Kafka manage its own metadata without an external consensus service. Kafka 3.3 (Oct 2022) marked KRaft production-ready. Kafka 4.0 (March 2025) made KRaft the only supported mode — ZooKeeper support was removed entirely. A 15-year architectural dependency dissolved.

The change wasn't just about killing ZooKeeper. It was about making Kafka's control plane look like its data plane: an event log that every controller replays, managed by the same familiar Kafka producer/consumer/replication machinery. Once you frame it that way, using Kafka's own battle-tested log to store metadata about Kafka is the obvious move — the puzzle is only why it took a decade.

The KIP

KIP-500 (2019) — "Replace ZooKeeper with a Self-Managed Metadata Quorum." Written by Colin McCabe, Jason Gustafson, Ismael Juma, and others at Confluent. Read the full KIP-500 proposal. The follow-ups — KIP-595 (Raft protocol), KIP-631 (quorum-based controller), KIP-833 (production-ready) — trace the whole multi-year migration.

What ZooKeeper actually did for Kafka

Before KRaft, ZooKeeper handled everything Kafka couldn't do for itself:

  • Controller election — one broker was elected "controller" via a ZooKeeper ephemeral znode (/controller). Whoever created it first won. When the controller died, the znode disappeared and everyone raced to recreate it.
  • Broker registration — each broker created an ephemeral znode under /brokers/ids/N. Session timeout → znode gone → broker considered dead.
  • Topic metadata /config/topics/T plus /brokers/topics/T/partitions/P. Partition-to-broker assignments, replication factors, ISR lists.
  • ACLs and quotas — user permissions, throughput limits, client rate limits.
  • Coordination watch notifications — every broker set watches on many znodes; ZooKeeper pushed change notifications so brokers could reload metadata.
Architecture — before and after KRaft
Before — Kafka + ZooKeeper
ZooKeeper Ensemble (3 nodes)ZK0ZK1ZK2Kafka BrokersB0controllerB1B2
6 processes to run. 2 quorum systems. Controller failover needs a cold read from ZK.
After — KRaft mode
Kafka Brokers (with embedded KRaft)Controller quorum (Raft)C0C1C2activeData brokers (tail metadata log)B0B1B2
3 processes to run. 1 quorum system. Standbys already caught up — sub-second failover.

This worked. But at large scale it broke in specific ways. Controller failover took tens of seconds in clusters with 200k+ partitions because the new controller had to read all metadata from ZooKeeper serially and re-establish watches. ZooKeeper's own quorum was a separate operational problem — with its own upgrades, security patches, network configuration, and monitoring. And for small deployments, you ran three ZooKeeper nodes purely to manage one broker. That's a lot of accidental complexity.

The KRaft architecture

In KRaft mode, some brokers run as controllers — typically 3 or 5, forming a Raft quorum. One is the active controller; the rest are standbys tailing the log. The metadata itself lives in a special internal topic called __cluster_metadata, which is just a Kafka topic — the same log-structured, replicated thing every other topic is — but managed by KRaft's consensus module rather than a normal broker.

Every metadata change is written as a record in the metadata log: "topic X created," "broker 7 joined," "partition P's ISR is now {5, 8, 12}." The active controller appends the record, replicates it via Raft, and once quorum is reached the record is committed. Every other broker in the cluster replays the metadata log as a passive follower — they don't need to be told individually about changes; they read them off the log like any Kafka consumer.

Metadata log — step 1 of 5
How a topic-create flows through KRaft.
adminC0C1C2B0B1B2__cluster_metadata log#1broker-2 joine…#2topic 'orders'…
Starting state — metadata log has 2 committed entries. All 3 controllers + all 3 data brokers are synced.

This is the elegant part: the whole cluster's state is a replicated log, and every broker's view of "current cluster state" is just its replayed position in that log. There's no watch-based invalidation. No cache coherence problem. The log is the source of truth.

Controller election in KRaft

The 3 or 5 controller nodes run standard Raft: heartbeats, election timeouts, terms (Kafka calls them epochs). When the active controller dies, another controller times out, becomes candidate, requests votes, wins, and becomes active. Because it was already tailing the metadata log as a Raft follower, it's already caught up — no cold-start metadata read from ZooKeeper. Failover is typically < 1 second even in massive clusters.

Controller election — step 1 of 5 · epoch 12
Active controller dies. Standby wins Raft election. Sub-second failover.
C0activeC1standbyC2standby
Steady state — Controller C0 is active in epoch 12. Standby controllers C1 and C2 tail the metadata log.

The migration timeline — why it took 6 years

KIP-500 was proposed in 2019. Kafka 4.0 finally removed ZooKeeper support in 2025. That's a long time for a "just replace ZooKeeper" change — because it was never just that. It required Kafka to grow a correct Raft implementation, a migration path from thousands of existing production ZooKeeper deployments, and a bridge mode where clusters ran with both systems simultaneously during upgrade.

Migration timeline — 6 years from KIP-500 to ZooKeeper removal
Kafka 2.8Apr 2021
KRaft ships in preview mode
Kafka 3.0Sep 2021
Bridge mode: dual-writes to ZK + KRaft
Kafka 3.3Oct 2022
KRaft marked production-ready (KIP-833)
Kafka 3.5Jun 2023
ZK-to-KRaft migration paths generally available
Kafka 3.9Nov 2024
Last release with ZK support
Kafka 4.0Mar 2025
ZooKeeper support removed. KRaft is now the only mode.

Confluent's enterprise customers reported that clusters that used to take 30-60 seconds to recover from a controller failure (200k+ partition environments) now recover in < 1 second. Small deployments no longer need to run 3 extra ZooKeeper nodes for a single Kafka broker. Operators get one system to patch and monitor instead of two.

Partition leader election — the other consensus in Kafka

There's a common confusion worth clearing up: Kafka has TWO layers of leadership.

  • Controller election — which broker manages cluster metadata. This is what KRaft (or ZooKeeper) does. There is one active controller per cluster.
  • Partition leader election — for each of the thousands of partitions, which broker is the leader replica that serves reads and writes. This is NOT a Raft election — it's a simple assignment written by the active controller into the metadata log. The controller picks a leader from the partition's ISR (In-Sync Replica set) and everyone reads that assignment.

So Raft happens once per cluster (for controllers), not once per partition. This is fundamentally different from CockroachDB or TiKV, which run MultiRaft — one Raft group per range. Kafka's partition replication uses ISR (In-Sync Replicas) semantics — related to Raft but simpler, since the controller does the deciding.

Where KRaft runs in production

Confluent Cloud

Managed Kafka at PB scale

Confluent runs Kafka clusters in AWS, GCP, and Azure. Their enterprise-tier clusters use KRaft to eliminate ZooKeeper ops overhead. Cluster spin-up went from ~5 minutes to ~30 seconds.

AWS MSK

Managed Streaming for Kafka

AWS MSK began offering KRaft-mode clusters in 2023. New clusters default to KRaft. AWS explicitly cites "faster failover, lower TCO, simpler operations" as the reasons.

LinkedIn

Where Kafka was born

LinkedIn wrote the original Kafka (2011). They migrated their in-house clusters to KRaft mode in 2023-2024. Their engineering blog reports controller-failover P99 dropped from 15s to 900ms.

Every new Kafka install

Kafka 4.0 default

As of Kafka 4.0 (March 2025), ZooKeeper is not just deprecated — it's removed. Fresh deployments have no other option. Existing ZooKeeper clusters must migrate before upgrading past 3.9.

The mental model — Kafka KRaft in one paragraph

3 or 5 brokers are designated controllers. They run Raft over an internal topic called __cluster_metadata. Every metadata change (topic created, broker joined, partition reassigned) is a record in that log. The active controller appends records; the standby controllers replicate them via Raft; all other brokers passively replay the log to know the cluster state. When the active controller dies, standby controllers hold a normal Raft election — a new leader emerges in < 1 second because it was already caught up. The whole cluster's state is a replayable log — no watches, no cache coherence, no ZooKeeper.

Key takeaways

  • KRaft eliminates the ZooKeeper dependency. One system to run, patch, and monitor instead of two.
  • Kafka has two layers of leadership: KRaft picks the cluster controller; the controller writes partition leader assignments into the metadata log. Only the first uses Raft.
  • Cluster metadata is stored in __cluster_metadata — a Kafka topic with Raft-driven replication. Every broker tails it. The log is the source of truth.
  • Controller failover: < 1 second in KRaft vs 15-60 seconds in ZooKeeper mode for large clusters — because standby controllers are already caught up.
  • Kafka 2.8 (2021) shipped KRaft as preview. Kafka 3.3 (Oct 2022) declared it production-ready. Kafka 4.0 (March 2025) removed ZooKeeper entirely.
  • KRaft is not MultiRaft — it runs one Raft group per cluster (for the controllers), not one per partition. Partition replication uses ISR semantics instead.

References

  • KIP-500 (2019) — Replace ZooKeeper with a Self-Managed Metadata Quorum.
  • KIP-595 (2020) — A Raft Protocol for the Metadata Quorum.
  • KIP-631 (2020) — The Quorum-based Kafka Controller.
  • KIP-833 (2022) — Mark KRaft as Production-Ready.
  • Kafka 4.0 release notes (2025) — ZooKeeper support removed.
  • Colin McCabe — "Apache Kafka Made Simple" (Confluent blog, 2020) — the KIP-500 vision essay.
  • Ongaro & Ousterhout (2014) — "In Search of an Understandable Consensus Algorithm." The Raft paper KRaft is based on.