Kafka Architecture
Kafka looks like a queue and behaves like a database log. This page builds the model from the ground up: why an append-only log is the primitive, how topics split into partitions and segments, how replication, the ISR and the high watermark make data durable, why ZooKeeper is gone, what makes it fast, how keys and retention shape your design, and how to size a cluster, ending with a session you can run locally in five minutes.
The Commit Log as a Primitive
Kafka was built at LinkedIn to move activity data between systems, and its central decision was to make the append-only log the thing you talk to, instead of a queue that deletes messages as they are read. A log is simple: records are added at the end, each gets a sequential number (the offset), and nothing is modified. Reading is a separate concern. A reader remembers its own position, and many readers can sit at different positions in the same log.
That one difference explains most of Kafka's behaviour: replay (move the offset back), independent consumers (each group keeps its own offset), retention by time or size rather than by consumption, and very high throughput (sequential appends and sequential reads are the cheapest thing a disk does).
| Traditional queue (RabbitMQ classic, SQS) | Log (Kafka) | |
|---|---|---|
| After a message is read | Deleted (acked) | Stays until retention removes it |
| Many independent consumers of one stream | Needs a copy of the message per consumer (fan-out exchange, SNS to SQS) | Free: each consumer group tracks its own offset |
| Replay history | Not possible once acked | Reset the offset to an earlier position or timestamp |
| Delivery tracking | Broker tracks every message (ack, redelivery) | Consumer tracks one number per partition |
| Per-message operations | Ack, nack, delay, priority, TTL, requeue | None: you move the offset |
| Work distribution | Competing consumers on one queue | Partitions assigned to the consumers of a group |
| Best at | Task queues, routing, per-message control | Event streams, high volume, replay, many consumers, stream processing |
- Kafka has no per-message acknowledgment, no delayed delivery, no priority and no "skip this one for now". A single slow message holds up every message behind it in its partition. If your workload is "run these jobs, retry the failed ones later", a queue (SQS, RabbitMQ) fits better. Choose Kafka when the stream of facts is the product.
Topics, Partitions, Offsets and Segments
A topic is a named stream. To scale it, a topic is split into partitions, and each partition is its own append-only log. Every record in a partition gets the next offset, a 64-bit sequence number that never changes and is never reused. A record is therefore addressed by topic + partition + offset.
| Term | Meaning | Why it matters |
|---|---|---|
| Topic | A named category of records, e.g. orders | The unit producers write to and consumers subscribe to |
| Partition | One ordered log of a topic, stored as a directory | The unit of ordering, parallelism and replication |
| Offset | Position of a record inside its partition | What consumers commit to remember their progress |
| Record | Key (optional), value, headers, timestamp | Key decides the partition; timestamp drives retention and time-based lookup |
| Segment | A file chunk of a partition (roll at 1 GiB or by time) | The unit of deletion: retention removes whole segments, never single records |
A partition is a directory of segment files. Only the newest segment (the active segment) is written to; older ones are immutable. This design is what makes retention cheap (delete a file) and lookups fast (binary-search a small index, then read forward).
- A segment rolls when it reaches
segment.bytes(default 1 GiB) or the age insegment.ms. The file name is the base offset, the offset of the first record inside it. - The
.indexis sparse: one entry per few KB of data. A lookup finds the nearest earlier entry and scans a short distance. The.timeindexdoes the same for timestamps, which is how "start from yesterday 09:00" works. - The log is only ever appended to, so writes are sequential. Old data is removed by deleting whole segment files.
The first line is a batch header: Kafka stores records in batches, and the producer id, epoch and sequence there are what make idempotent producers work (page 03). Output above is abridged and shows the shape, not exact values.
- Offsets increase within a partition only. Two records with keys that hash to different partitions have no defined relative order, even if one was sent a millisecond before the other. Anything that needs "A before B" must share a key (page 01, ordering).
Brokers, Leaders and Followers
A Kafka cluster is a set of brokers (servers). Each partition is stored on several brokers, controlled by the topic's replication factor. For each partition exactly one replica is the leader and the rest are followers.
orders-1 from cluster metadata and writes only to that broker. Producers never write to followersclient.rack)auto.leader.rebalance.enable=true (default) leadership is later moved back to balance the loadOne broker, the active controller, manages the cluster: it detects broker failures, elects partition leaders, and publishes the result as cluster metadata. How the controller is elected and where the metadata lives changed fundamentally in recent Kafka versions (chapter 05).
| Role | Count per partition | Serves reads/writes | Can become leader |
|---|---|---|---|
| Leader | 1 | Yes | n/a |
| Follower in the ISR (in sync) | RF - 1 minus laggards | No (follower fetch aside) | Yes |
| Follower out of the ISR (lagging) | varies | No | No (unless unclean election is enabled) |
Replication, the ISR and the High Watermark
Replication is where Kafka's guarantees come from, and the settings interact, so it pays to understand the moving parts before touching them.
| Term | Meaning |
|---|---|
| Replication factor (RF) | Number of copies of each partition, including the leader. Set per topic. Production default: 3 |
| ISR (in-sync replicas) | The leader plus the followers that are caught up. A follower is dropped from the ISR if it has not caught up within replica.lag.time.max.ms (default 30 s) and re-added when it catches up |
| Log end offset (LEO) | The next offset a replica will write |
| High watermark (HW) | The highest offset replicated to all ISR members. Consumers can only read below the HW, so they never see data that could be lost by a leader failover |
| Leader epoch | A counter that increases at each leader change; followers use it to truncate divergent data safely after a failover |
| Setting | Where | Default (4.0) | Role |
|---|---|---|---|
acks | Producer | all | 0: do not wait. 1: wait for the leader only. all: wait for every in-sync replica (at least min.insync.replicas) |
min.insync.replicas | Topic / broker | 1 | Minimum ISR size for an acks=all write to succeed. If the ISR is smaller, the write is rejected (NotEnoughReplicasException) |
replication.factor | Topic | broker default.replication.factor = 1 | Number of copies. The broker default of 1 is for development only |
acks=all alone is not a guarantee. If the ISR has shrunk to only the leader and min.insync.replicas=1, acks=all means "the leader has it", which is the same as acks=1. The pair **RF=3, min.insync.replicas=2, acks=all** is the standard durable setting.
| RF | min.insync | acks | One broker down | Two brokers down |
|---|---|---|---|---|
| 3 | 2 | all | Writes continue, no loss | Writes rejected; reads continue; no loss |
| 3 | 1 | all | Writes continue | Writes continue on the lone leader; if it then dies, acknowledged data is lost |
| 3 | 2 | 1 | Writes continue | Acknowledged after leader write only; lost if the leader dies before followers copy |
| 1 | 1 | all | Partition unavailable | Unavailable; data lost if the disk is lost |
- If every in-sync replica is gone and only an out-of-sync follower remains, Kafka can either wait (partition unavailable) or elect the stale follower (available, but it is missing the newest records, which then disappear).
unclean.leader.election.enabledefaults to false: Kafka chooses consistency over availability. Enable it only for data you can afford to lose, such as metrics.
- Topic:
replication.factor=3,min.insync.replicas=2. - Producer:
acks=allandenable.idempotence=true(both are the default since Kafka 3.0, but state them explicitly so a reader and a code review can see the intent). - Spread the replicas across availability zones (
broker.rack) so a zone failure leaves at least two in sync.
ZooKeeper to KRaft
For its first decade Kafka stored cluster metadata (topics, partitions, leaders, configs, ACLs) in Apache ZooKeeper. That meant running and operating a second distributed system, and the controller had to reload all metadata from ZooKeeper after a failover, which limited cluster size and recovery time. KRaft (Kafka Raft) removes the dependency: a set of controller nodes run the Raft consensus protocol and keep the metadata in an internal, replicated Kafka log named __cluster_metadata. Brokers are consumers of that log.
| Date / version | Event |
|---|---|
| Kafka 2.8 (2021) | KRaft available as early access |
| Kafka 3.3 (2022) | KRaft declared production-ready for new clusters |
| Kafka 3.9 | The last release that supports ZooKeeper; the bridge for migrating existing clusters |
| Kafka 4.0 (March 2025) | ZooKeeper removed. KRaft is the only mode. Existing ZooKeeper clusters must first be migrated to KRaft, via 3.9, before upgrading to 4.x |
process.rolesisbroker,controlleror both. Small clusters and local setups combine them; production clusters usually run 3 dedicated controller nodes so a broker overload cannot destabilise metadata.- The controller quorum needs a majority: 3 controllers tolerate 1 failure, 5 tolerate 2. An even number adds cost without adding tolerance.
- Kafka 4.x also supports dynamic controller quorums (
controller.quorum.bootstrap.servers) so controllers can be added or removed without a restart; the staticcontroller.quorum.votersform above remains valid. - Storage must be formatted with a cluster id before first start (
kafka-storage.sh format). The official Docker image does this for you.
- If you read an older tutorial that starts ZooKeeper, ignore that part: the
zookeeper.connectproperty andzookeeper-server-start.shno longer exist in 4.x. - Clients are unaffected.
bootstrap.serversstill points at brokers; applications never talked to ZooKeeper. - Kafka 4.0 brokers and tools need Java 17; the Java clients run on Java 11 or later.
Why Kafka Is Fast
Kafka routinely handles hundreds of thousands of records per second per broker on ordinary hardware, with durable storage on disk. There is no trick; it follows the grain of the machine in five ways.
| Technique | What happens | Effect |
|---|---|---|
| Sequential I/O | Appends to the end of a file; reads scan forward | Disks, and SSDs even more so, are far faster at sequential than random access |
| OS page cache | Kafka does not keep its own cache; recent segments live in the operating system's page cache | A consumer that keeps up reads from RAM. No JVM heap, no garbage collection for cached data, and the cache survives a broker restart |
| Zero-copy transfer | For plaintext connections the broker uses sendfile: data goes from page cache to the network card without copying through the JVM | Far less CPU and memory bandwidth. Not available with TLS, which must encrypt in user space |
| Batching | Producers group records per partition (batch.size, linger.ms); the broker stores and ships whole batches | Amortises network and disk overhead across many records |
| Compression | A whole batch is compressed once by the producer and stays compressed on disk and on the wire (compression.type: gzip, snappy, lz4, zstd) | Less network and disk; consumers decompress |
| Partitioned parallelism | Partitions spread across brokers and consumers | Throughput scales by adding partitions and machines |
| Producer setting | Default (4.0) | Effect of raising it |
|---|---|---|
linger.ms | 5 | Wait longer to fill batches: more throughput and compression, more latency. (Was 0 before 4.0) |
batch.size | 16384 (16 KiB) | Larger batches per partition: better throughput, more memory |
compression.type | none | lz4 or zstd usually pay off for JSON/text |
buffer.memory | 33554432 (32 MiB) | More room when the broker is slow before send() blocks (max.block.ms, default 60 s) |
- Calling
flush()after everysend()defeats batching: each record becomes its own request, and throughput can drop by an order of magnitude. Let the producer batch, and use callbacks or theFutureonly to detect failures.
Keys, the Partitioner and Skew
The partition a record lands in decides its ordering group, its consumer, and the load on the broker. The producer's partitioner chooses it:
| Case | Default behaviour | Consequence |
|---|---|---|
| Record has a key | toPositive(murmur2(keyBytes)) % numPartitions | Same key always goes to the same partition (as long as the partition count does not change) |
| Record has no key | The producer fills a batch for one partition, then moves to another (sticky, load-aware since Kafka 3.3) | Even spread, no ordering guarantee between records |
| Explicit partition number | Used as given | Full control; you own the balance |
- Use the entity whose order matters:
customerId,accountId,orderId. All events of that entity then stay ordered. - Pick a key with many distinct values and an even spread. A key like
countrywith 80% of traffic from one value sends 80% of the load to one partition: a hot partition, one overloaded broker and one lagging consumer while the others idle. - If one key is genuinely hot, salt it (
tenantId + "-" + (n % 8)) and accept that order now holds only per salted key.
- You can add partitions, never remove them. But the formula uses
numPartitions, so after growing from 3 to 6 the keyc-17can map to a different partition. New events ofc-17then go to a partition that does not contain its history, and per-key ordering is broken across the change. Consumers that keep per-key state also break. - Plan the partition count up front with headroom. If you must grow a keyed topic, create a new topic with the new count and migrate.
Delete vs Compact
Kafka keeps data according to the topic's cleanup.policy, which can be delete, compact, or both.
| Policy | Rule | Use for | Key defaults (4.0) |
|---|---|---|---|
delete | Remove whole segments older than retention.ms or when the partition exceeds retention.bytes | Event streams: orders, clicks, logs | retention.ms = 7 days (log.retention.hours=168), retention.bytes = -1 (unlimited), checked every 5 min |
compact | Keep at least the latest record for each key; older records with the same key are removed in the background | State: latest customer profile, config, changelog topics, consumer offsets (__consumer_offsets) | min.cleanable.dirty.ratio = 0.5, min.compaction.lag.ms = 0, delete.retention.ms = 1 day |
compact,delete | Compact, and also drop everything older than the retention time | Bounded state history | Both sets above |
- Retention deletes a segment only when all records in it are old enough, and never touches the active segment. A quiet topic can therefore keep data longer than
retention.ms. Lowersegment.msif that matters. - Compaction never changes offsets and does not reorder: it only removes superseded records. A consumer reading a compacted topic from the start sees the latest value for every key, plus some older ones that have not been cleaned yet.
- A record with a key and a null value is a tombstone: it marks the key as deleted. It is retained for
delete.retention.msso slow consumers can observe the delete, then removed. - Compaction requires keys: producing a record without a key to a compacted topic is rejected.
- Compaction runs in the background on closed segments once the dirty ratio is high enough. A compacted topic will still contain old duplicates for a while, so consumers must still handle several values per key and apply them in order. Do not use compaction as a substitute for a database query.
How Many Partitions? How Much Disk?
Partition count is the hardest setting to change later, so size it from numbers. Use measured throughput from your own producers and consumers; the figures below are examples of the method, not benchmarks.
| Quantity | Example value | Notes |
|---|---|---|
| Target throughput for the topic (T) | 100 MB/s | Peak, with growth headroom |
| Measured producer throughput per partition (P) | 10 MB/s | Test with real record size and compression |
| Measured consumer throughput per partition (C) | 5 MB/s | Often the slower side: includes your processing time and DB writes |
| Partitions needed | max(T/P, T/C) = max(10, 20) = 20 | Consumers are the bottleneck, so size for them |
| Headroom | Round up to 24 or 30 | A number with many divisors lets you run 2, 3, 4, 6... consumers evenly |
The partition count is also the ceiling on consumer parallelism in one group: 24 partitions means at most 24 active consumers. More consumers than partitions sit idle.
- More open file handles and memory per broker (each partition has segment files and indexes).
- Longer recovery: after a broker failure, every partition it led needs a new leader election.
- More replication traffic and end-to-end latency, since each partition replicates independently.
- Each consumer has more partitions to track and each rebalance moves more state.
- KRaft raised the practical ceiling a lot compared with ZooKeeper, but "as many as possible" is still the wrong default.
| Item | Value |
|---|---|
| Ingest | 20 MB/s average |
| Retention | 7 days = 604,800 s |
| Raw data | 20 x 604,800 = 12.1 TB |
| With replication factor 3 | 36.3 TB across the cluster |
| With 2:1 compression on disk | About 18 TB |
| Operating headroom | Plan for no more than about 60 to 70% full, so brokers can recover and rebalance: roughly 26 to 30 TB provisioned |
replication.factor=3,min.insync.replicas=2.- Set
auto.create.topics.enable=falseon brokers (default istrue, and a typo in a topic name silently creates a new one with default settings: 1 partition, RF 1). - Create topics through code or tooling (Terraform,
kafka-topics.sh, SpringNewTopicbeans) so partition count, RF and retention are reviewed, not accidental.
Run a Broker and Look Inside
Nothing makes the previous chapters stick like watching them happen. This needs only Docker. The official image runs a single combined broker+controller in KRaft mode.
Output is abridged; your build may print extra columns such as the topic id. With one broker, replication factor can only be 1. On a real cluster you would see different leaders and a longer ISR list.
Both c-17 records share a partition, so they stay in order. The partition numbers you see depend on the hash, but the grouping is always the same. The console consumer prints partitions in whatever order it fetched them, which is also a live demonstration that there is no order across partitions.
The three numbers are the log end offset of each partition (1, 0 and 2 records). __cluster_metadata-0 is the KRaft metadata log from chapter 05, and __consumer_offsets-* is where consumer groups commit their positions. The log directory path comes from the image's default configuration and may differ between image versions.
docker rm -f kafkaremoves the container and its data.
Questions You Should Be Able to Answer
Crisp answers for the questions that come up in interviews on this topic.
- Q: How is Kafka different from a message queue?
Kafka is a distributed, partitioned, replicated log. Records are retained for a period regardless of consumption, consumers track their own offsets, and many consumer groups can read the same data independently and replay it. A queue deletes a message once acknowledged and tracks delivery per message. - Q: What is a partition and why does Kafka have them?
A partition is one ordered append-only log of a topic. Partitions give parallelism (one consumer per partition within a group), scalability (spread across brokers) and the unit of ordering (order is guaranteed only within a partition). - Q: What is an offset?
A sequential, immutable position of a record within one partition. A consumer commits the next offset it wants to read, so after a restart it resumes where it left off. - Q: How does Kafka guarantee ordering?
Only within a partition. Records with the same key hash to the same partition, so they stay in order for a single consumer of that partition. There is no ordering across partitions. - Q: Explain leader, follower, ISR and the high watermark.
Each partition has one leader that serves all reads and writes; followers replicate from it. The ISR is the set of replicas caught up withinreplica.lag.time.max.ms. The high watermark is the highest offset replicated to all ISR members; consumers read only below it. - Q: How do you configure Kafka so that you never lose acknowledged data?
Replication factor 3,min.insync.replicas=2, produceracks=allwith idempotence enabled,unclean.leader.election.enable=false, and replicas across racks or zones. This tolerates one broker failure without loss and refuses writes rather than risk loss when two are down. - Q: What does
acks=allmean ifmin.insync.replicas=1?
The leader alone can satisfy it when the ISR has shrunk to one member, so it is effectivelyacks=1.acks=allis only as strong asmin.insync.replicas. - Q: What changed in Kafka 4.0?
ZooKeeper support was removed; KRaft is the only metadata mode. Controllers run Raft and store metadata in the__cluster_metadatalog. The new consumer group protocol (KIP-848) became generally available, andlinger.msdefault became 5 ms. Brokers need Java 17. - Q: Why is Kafka fast?
Sequential disk I/O, reliance on the OS page cache, zero-copysendfilefor plaintext, batching and compression of whole record batches, and horizontal scaling through partitions. - Q: Delete vs compact cleanup?
deleteremoves whole old segments by age or size.compactkeeps the latest record per key and removes older ones in the background; tombstones (null values) mark deletions. Compacted topics still contain old duplicates for some time. - Q: Why is changing the partition count of a keyed topic risky?
The key-to-partition mapping depends on the partition count, so existing keys can move to different partitions, breaking per-key ordering and any consumer state keyed by partition assignment. Partitions can be added but never removed. - Q: How many partitions should a topic have?
Divide target throughput by the measured per-partition throughput of the slower side (usually the consumer), add headroom, and remember it caps consumer parallelism. Too many partitions cost memory, file handles, recovery time and replication overhead.
Cheat Sheet and Recap
| Concept | Remember | Default / value (Kafka 4.0) |
|---|---|---|
| Record address | Topic + partition + offset | Offsets are per partition |
| Ordering | Only inside one partition | Key decides the partition |
| Segment size | Retention deletes whole segments | segment.bytes = 1 GiB |
| Retention | Time or size, not consumption | 7 days; retention.bytes unlimited |
| Durable write | RF 3 + min ISR 2 + acks=all | acks=all and idempotence are on by default |
min.insync.replicas | Floor for the ISR on acks=all writes | 1 (raise to 2) |
| Unclean election | Availability over consistency if enabled | false |
| Replica lag limit | Leaves the ISR after this | replica.lag.time.max.ms = 30 s |
| Metadata | KRaft only, no ZooKeeper | Removed in 4.0 |
| Compaction | Latest value per key, tombstone = delete | delete.retention.ms = 1 day |
| Partition count | Can grow, never shrink; breaks key mapping | Broker default num.partitions = 1 |
- Kafka is a log, not a queue. Reading moves an offset; it does not delete.
- The partition is everything: the unit of order, parallelism, replication and deletion.
- One leader per partition; followers copy. The ISR and the high watermark decide what is safe to read and to acknowledge.
- **Durability = RF 3 +
min.insync.replicas=2+acks=all**, not any one setting alone. - KRaft replaced ZooKeeper, and ZooKeeper is gone as of 4.0.
- Choose keys for even spread and entity ordering; never grow a keyed topic casually.
- Size from measurements: partitions = max over producer and consumer ceilings, disk = rate x retention x RF, with headroom.
Defaults and version facts on this page were checked against the Apache Kafka 4.0 configuration reference and release announcement on 2026-10-31. Kafka releases move on; re-check a value in the docs for your version before relying on it in production.
- 03 Kafka Producers & Consumers - the client side: batching, idempotence, transactions, consumer groups, offsets and rebalancing.
- Messaging Fundamentals - the delivery guarantees these settings implement.
- System Design: Data - how a log-based architecture fits into larger designs.