Kafka Fundamentals
Kafka started as a solution to LinkedIn's integration problem: too many services sending data directly to too many other services. The fix was a durable, replicated, append-only log that producers write to once and any number of consumers can read independently. This guide covers brokers, partitions, replication, and consumer groups — the pieces that make that log model actually work.
What is Kafka?
Kafka is a distributed, replicated, append-only log — designed to move billions of records per day between systems with strong ordering and durability guarantees.
That single sentence hides three ideas doing all the work. First, Kafka is distributed: the data lives on many machines at once, so a single machine failing doesn't take the system down. Second, it is replicated: every piece of data is copied to more than one machine, so a machine failing doesn't lose data. Third, it is an append-only log: writers add to the end and never modify what came before. That third property is the least glamorous but by far the most important — every other property Kafka is famous for is a consequence of it.
It helps to compare Kafka to what it isn't. A traditional message queue like RabbitMQ or ActiveMQ treats a message as a transient item: the broker delivers it, the consumer acknowledges, and the broker deletes it. Kafka treats a message as retained data on a log: it stays on disk for as long as the topic's retention or compaction policy keeps it, and any consumer can replay it from any offset still available. The message is not delivered — it is made available, and consumers pull what they need at their own pace. Consumption and retention are decoupled: a consumer reading a record does not remove it, and retention eventually does — Kafka is not a permanent, forever-growing log by default.
This single design choice — the log is the truth — is what makes Kafka usable simultaneously as a message queue, a stream-processing substrate, an event-sourcing store, and a change-data-capture backbone. It's not that Kafka happens to be good at four things; it's that all four are the same thing when your underlying primitive is a durable, ordered log.
The defining characteristics
Before we look at the pieces, it's worth being explicit about the properties Kafka guarantees. Everything in the rest of this chapter book is written to preserve them.
Durability
Once Kafka acknowledges a write, it will not lose that record under the failure model you configured — typically "any single broker can die" with a replication factor of 3 and acks=all. Durability is a knob, not a fixed guarantee.
Ordering
Records within a partition are strictly ordered by the offset they receive. This ordering is preserved across replicas, across broker restarts, and across consumer restarts. There is no ordering across partitions of the same topic — this trade-off is what lets Kafka scale horizontally.
Replayability
Because Kafka stores records rather than delivering them, any consumer can rewind to any past offset and re-read. This is what makes stream-processing recovery, exactly-once semantics, and back-fills possible without extra infrastructure.
Horizontal scalability
Every dimension of Kafka scales by adding machines: more brokers for storage and throughput, more partitions for parallelism, more consumers for processing capacity. Nothing requires vertical scaling of a single node.
Decoupling of producers and consumers
A producer doesn't know or care who is consuming its data. A consumer doesn't know or care who is producing. They meet only at the log. This is what turns Kafka from a pipe into an event backbone.
Visual Learning
Watch one Kafka cluster handle writes, replication, consumer groups, failures, ordering, and retention as a single continuous system — not twelve separate explanations.
Why Kafka Exists
Kafka wasn't designed in an ivory tower. It was built at LinkedIn in 2010 because every existing option had already failed them at scale.
The pipeline explosion problem
Modern systems often start with direct point-to-point integrations: service A sends to B, B forwards to C, C aggregates for D. That works briefly. Then every new producer or consumer adds another brittle connection to build, monitor and evolve.
With N producers and M consumers, you can end up with N × M integrations. LinkedIn hit that wall early: too many services, too many downstream systems, and too much coordination every time schemas or consumers changed.
Kafka's founding idea was simple: one bus, many independent subscribers. Every producer publishes to the bus once. Every consumer subscribes to what it needs. The N × M integration problem becomes N + M.
The speed-mismatch problem
Downstream systems also consume at different speeds. A search indexer may be near real time while a warehouse load trails by hours. Direct pipelines couple them. Kafka's answer is retention: producers write once, and each consumer reads the same log at its own pace.
The freshness problem
Kafka also collapsed the old trade-off between durable batch pipelines and fast but forgetful real-time queues. The same log can feed an overnight warehouse load and a low-latency streaming application. Batch and real time become different ways of reading the same data.
“The log is the simplest possible storage abstraction. It is an append-only, totally-ordered sequence of records ordered by time. It looks like this: [record][record][record]. That's it.”
— Jay Kreps, one of Kafka's original authors
Kafka Internals
Kafka internals come down to four mechanisms that the rest of this guide unpacks. Each partition is an append-only log that brokers write to sequentially. Each partition has one leader that serves reads and writes, while followers replicate it and form the in-sync replica set (ISR). Consumers pull records and track their own position with offsets, so reading never removes data. A controller (a KRaft quorum in current Kafka) tracks which brokers are alive and which broker leads each partition.
The Kafka architecture section below walks through the brokers, controller, and clients; the partition log and replication chapters go deeper into the log and the ISR.
Kafka Architecture Diagram at a Glance
Kafka's architecture is deliberately boring. That is its strength.
A Kafka cluster is a group of processes called brokers that collectively store the log data for every topic. Producers and consumers are client libraries running inside your applications — they never share Kafka's process boundaries. There are no gateways, no proxies, no middlemen. A client library opens a TCP connection to a broker, speaks Kafka's binary protocol, and moves on. The Kafka architecture diagram below shows the three moving parts: producers, brokers, and consumers.
Brokers
Each broker is one JVM process running on one host. It stores copies of some partitions, accepts producer writes, serves consumer fetches, and replicates with its peers. Kafka is fast partly because a broker does only those few things; it is not trying to be a general-purpose database.
The controller
Cluster metadata — which brokers are alive, which partitions live where, which broker currently leads each partition — is managed by a controller. Current Kafka manages this metadata through KRaft, a Raft-based controller quorum running across a small set of brokers rather than a single elected broker; don't picture "one broker is the controller" as the general architecture. When a broker dies, the controller quorum notices, picks a new leader from the surviving replicas, and propagates the new metadata to everyone. Older Kafka releases used an external ZooKeeper ensemble for this role; ZooKeeper support was removed in Kafka 4.0, so that mode is historical in current Apache Kafka and is called out here only so you recognize it in older deployments or documentation.
Producers and consumers
Producers are library code that lives in your application. When you call producer.send(record), the library serializes the record, chooses a partition, batches records per partition, and sends them to that partition's leader broker. Batching and compression are a big part of Kafka's throughput story because they amortize network overhead across many records.
Consumers are also library code. They coordinate via Kafka's group protocol to divide partitions among themselves, poll their assigned partitions for new records, and commit progress back to Kafka. Everything about how fast, how much, and in what order a consumer reads is under the consumer's control — Kafka never pushes.
The optional ecosystem
Around the core cluster sits an ecosystem you'll often meet in production. Kafka works without these pieces, but they solve common operational problems.
Schema Registry
A separate service that stores schemas and enforces evolution rules across producers and consumers. It is not part of Apache Kafka, but it is a common answer to "how do we change message structure safely?"
Kafka Connect
A framework for moving data into and out of Kafka using reusable source and sink connectors. If you're writing lots of boilerplate just to copy data between Kafka and another system, Connect is often the missing tool.
Kafka Streams
A JVM library for stream processing — filtering, joining, aggregating and windowing — that runs inside your application rather than on a separate cluster.
ksqlDB
A SQL layer over Kafka Streams for teams that prefer expressing stream-processing logic declaratively.
Core Concepts
The core vocabulary below covers most Kafka design conversations. Learn it precisely once and you'll never be lost in a design review.
Producer
A client that publishes records to a topic. Producers buffer, batch and compress records before sending them, which is why producer tuning often revolves around batch size and linger rather than raw per-message speed.
Consumer
A client that reads records from partitions. Consumers are pull-based — Kafka never pushes to them. Each consumer tracks its own position per partition and usually commits that position so it can resume after a restart.
Broker
A single Kafka server — one JVM process on one host. Brokers collectively store topic data; for each partition copy they host, they act either as the leader serving reads and writes or as a follower replicating from the leader.
Topic
A named log. "Orders", "clicks", "user-events" — a topic is just an identifier that groups related records together. Topics are multi-producer (any number of producers can publish to the same topic) and multi-subscriber (any number of consumers, in independent groups, can read the same topic). Under the hood a topic is split into partitions, but from a user's perspective it's one logical stream.
Partition
The unit of parallelism and ordering inside Kafka. A topic is divided into some number of partitions — the right count depends on required consumer parallelism, key cardinality and target throughput, not a fixed list like "3, 6, 12, 24" — and each partition is an independent, ordered log. Ordering is preserved within a partition; there is no ordering across partitions. This is the trade-off that lets Kafka scale horizontally: to increase throughput, you can add partitions and spread them across brokers and consumers. More partitions does not automatically mean proportionally more throughput — broker, network and downstream capacity still bound it, and more partitions add metadata and rebalance overhead.
Offset
A record's position inside a partition, not a global Kafka message id, a timestamp, or proof the record stays readable forever. Offsets are monotonically increasing integers assigned by the broker when a record is appended. The first record in a partition gets offset 0, then 1, then 2, and so on. Offsets never reset, even after retention deletes old data, so "read from offset 42" means the same thing today as it will in a month — unless retention or compaction has already removed that offset from disk. A consumer's committed offset is conventionally the next offset the consumer will read, not simply "the last record it processed" — that distinction matters when reasoning about what a crash before or after commit actually re-delivers or skips (see Consumer Offsets below).
Consumer group
A named set of consumers that share the work of consuming a topic. Within one active group assignment, each partition is owned by at most one active member — this is what preserves per-partition ordering under parallel consumption. It does not by itself prevent duplicate processing: a consumer can crash after processing a record but before its offset commit is durable, and the record is reprocessed after reassignment (see Consumer Offsets below). Two consumers in the same group split the work; two consumers in different groups both get the full stream. Consumer groups are cheap: use one per logical application, not one shared across teams.
Replication factor
How many copies of each partition exist across brokers. RF=3 is a common production choice: three brokers each hold a copy of every partition assigned to them, so one broker failure still leaves two replicas with every record and a fast leader failover. Whether RF=3 (or higher) is appropriate depends on your durability requirements and failure domains — replicas placed in the same rack or availability zone can fail together, which RF alone does not fix (see Replication). RF=1 has no replication and is unsafe for anything you cannot afford to lose.
Leader and Follower
For every partition, one broker is elected the leader. It serves all reads and writes; it also serves as the origin from which followers replicate. The other brokers holding a copy are followers — they pull records from the leader and append them to their local logs. Followers do not serve consumer reads (with a rare geo-latency exception in newer Kafka). If a leader dies, one of the followers is promoted. From the client's perspective, this all happens invisibly in a few hundred milliseconds.
ISR (In-Sync Replicas)
The subset of a partition's replicas that Kafka currently considers caught up enough to count for durability and leader election. The leader itself is always in the ISR. With acks=all, the producer waits for the current ISR, and min.insync.replicas decides how small that ISR may shrink before writes are rejected.
Retention
The rule Kafka uses to delete old records, independent of whether consumers have read them. Topics can delete by time or size. Compaction is different: over time it keeps the latest value per key, which is useful for changelogs and state rebuilds but is not a promise that a topic always contains exactly one record per key.
The Partition Log — Kafka's atom
If you remember only one diagram, make it this one. Every other property of Kafka is a consequence of what happens in a partition log.
A partition is not a queue, and not literally one file either. It is an ordered append-oriented log made of segment files plus indexes. Kafka writes to the active segment, closes older ones behind it, and lets retention or compaction clean whole segments over time.
Anatomy of a record
Each record has a small amount of metadata plus the payload your application cares about:
Offset
The record's position in the partition — assigned by the broker at write time, monotonically increasing, and used by consumers to identify where they are.
Key (optional)
Bytes used for partition routing and log compaction. If a producer supplies a key, Kafka's default partitioner hashes it to choose a partition, guaranteeing all records with the same key land on the same partition (and therefore in order relative to each other).
Value
The payload — bytes to Kafka. To your application it's usually a JSON document, an Avro record, a Protobuf message, or occasionally raw binary data. Kafka does not care what's inside.
Timestamp
Usually either the producer's event time or the broker's append time. Kafka uses it for time-based retention and time-based lookups.
Headers
Extra key-value metadata such as trace ids or schema ids. Useful to consumers, but not something Kafka indexes or queries.
What happens to ordering when you add partitions
A keyed producer relies on a stable key → partition mapping to preserve per-key order. That mapping depends on the current partition count. If a topic grows from 6 partitions to 12, a common hash-based partitioner may start routing customer-42 to a different partition. Old events stay in the old partition; new ones go to the new partition. Nothing is lost, but reconstructing a single ordered history for that key now requires reading and merging across both partitions.
Why the log format matters
The log format is a major reason Kafka is fast. Appends happen at the tail, reads usually scan forward from an offset, and records are not modified in place. That removes the read-vs-update contention you get in mutable storage, even though Kafka still has to coordinate replication, group membership and transactional visibility.
Replication and the ISR
The log format explains why Kafka is fast. Replication explains why it doesn't lose your data.
Every partition has a replica set — a fixed list of brokers that hold a copy. One of those replicas is the leader; the rest are followers. The leader accepts producer writes; the followers pull records from the leader and append them to their local logs, in the same order, at the same offsets.
What in-sync actually means
A follower is in-sync when Kafka's replication protocol considers it caught up with the leader — concretely, when it has not failed to fetch and apply the leader's latest records for longer than replica.lag.time.max.ms. If a follower falls behind that window — because its host is slow, its disk is saturated, its network is congested, or its JVM is in a long GC pause — Kafka removes it from the ISR until it catches up again. The leader is always counted as part of its own ISR.
The ISR is not a fixed list. It shrinks and grows dynamically as followers get slower or faster. This adaptive membership is what makes Kafka's durability guarantees so cheap — a temporarily slow broker doesn't block the whole cluster, it just doesn't count for durability decisions until it's healthy again.
The acks / min.insync.replicas contract
The producer's acks setting decides when a write is considered successful:
acks = 0
The producer doesn't wait for any acknowledgement. Fire-and-forget. Fastest, but the record may be lost if the network or the broker fails between send and append. Used almost only for high-volume metrics where individual losses don't matter.
acks = 1
The producer waits for the leader to append the record — but not for followers to replicate it. If the leader crashes after appending but before replicating, the record is lost and no follower has it. This was the producer default before Kafka 3.0 (since 3.0 the default is acks=all with idempotence enabled); it's a footgun for anything you care about.
acks = all
The producer waits until every replica currently in the ISR has appended the record. acks=all does not mean every replica of the partition, configured or not — it means every replica Kafka currently considers in-sync, which can be fewer than the replication factor if the ISR has shrunk. Combined with min.insync.replicas ≥ 2, Kafka rejects the write instead of accepting it with a shrunk ISR, so a successful acks=all write means at least min.insync.replicas brokers have it — not that every broker that could ever hold a copy does.
The broker side of the contract is min.insync.replicas. This tells the broker: "if the ISR is smaller than this number, reject `acks=all` writes". Setting it to 2 with a replication factor of 3 gives you a well-known trade-off: you tolerate 1 broker failure without either data loss (because 2 replicas still have the record) or availability loss (because the write still succeeds). If a second broker fails, writes start failing loudly — which is the correct behaviour for a system that would otherwise be one failure away from data loss.
Log end offset, high watermark and last stable offset
Three offsets matter once replication is involved, and conflating them is a common source of confusion. The log end offset (LEO) is the next offset a replica will assign — effectively "how far this replica's log has grown", tracked independently per replica. The high watermark (HW) is the leader's replicated/committed visibility boundary: the point up to which the leader knows the current ISR has durably replicated, and therefore the point up to which consumers are allowed to read. A record can sit past the HW on the leader's disk — written, but not yet safely replicated — without being visible to consumers yet.
Thinking of the high watermark as roughly "the minimum replicated progress across the ISR" is a useful mental model, but Kafka has to track it more carefully when ISR membership changes or leaders change. For transactional producers, there is also the last stable offset (LSO), which holds back records from uncommitted or aborted transactions for read_committed consumers.
Producer idempotence and Kafka transactions
Idempotent producer (enable.idempotence=true) solves one specific problem: if a producer retries a send because it didn't receive an acknowledgement, the broker recognizes the retry (via a producer id and a per-partition sequence number) and does not append a second copy. That is exactly-once for that specific retry scenario at the log-append level — it does not know whether your application already submitted the same logical command twice through a different path, and it does not make a database write or an HTTP call exactly-once.
Kafka transactions go further: they can atomically group multiple partition writes together with consumer offsets for read-process-write workflows that stay inside Kafka. A read_committed consumer will not see data from a transaction that later aborts. What transactions do not do is make external systems — a database update, payment call or email send — part of the same atomic unit automatically.
Consumer Groups and Parallel Consumption
A single Kafka topic can be consumed by one script or by a thousand-node fleet. The mechanism that makes both possible is the consumer group.
A consumer group is a set of consumers that agree to share the work of reading a topic. Inside a group, Kafka guarantees that each partition is owned by exactly one member. This is the key invariant — the one that preserves ordering under parallel consumption. Within a stable assignment, two consumers in the same group are never assigned overlapping partitions, so they do not process the same record concurrently (after a rebalance, or a crash before an offset commit, a record can be redelivered to a different member).
How assignment works
When a consumer joins a group, it announces itself to the group's coordinator — a specific broker chosen by hashing the group id. The coordinator collects the current members, runs the configured partition assignor, and hands each member its list of assigned partitions. Membership or subscription changes — a consumer joining, leaving, crashing, missing its session timeout, or a topic's partition count changing — cause the coordinator to reconcile ownership, commonly called a rebalance. Exactly how disruptive that is depends on the assignor and protocol in use: eager-style reassignment can briefly pause the whole group, while incremental/cooperative assignment limits the pause to only the partitions actually changing hands. Verify the current default and behavior against your Kafka client version rather than assuming one assignor is permanently "the modern one".
The upper bound on parallelism inside a group is the partition count. If a topic has 6 partitions and a group has 10 consumers, only 6 are assigned a partition — the other 4 sit idle with nothing assigned. They are not instant hot standbys: if one of the 6 crashes, Kafka first has to detect the failure (missed heartbeats or a session timeout), then trigger a rebalance to reassign that partition to one of the idle members — a real, if usually short, interruption, not an instantaneous failover. Adding more consumers than partitions doesn't add throughput; it adds idle capacity that only helps once a rebalance has completed.
Consumer offsets: what a commit actually promises
A committed consumer offset conventionally represents the next offset this consumer group will read for that partition — not a generic "the last record we successfully finished processing". That distinction drives both ways a consumer can misbehave around a crash.
Process, then crash before commit: the consumer fetches offset 100, processes it (maybe writing to a database or calling an API), and crashes before the corresponding commit becomes durable. A replacement consumer resumes from the last committed offset, which is still behind 100 — so record 100 is fetched and processed again. This is the at-least-once shape, and it is why idempotent processing matters more than "did Kafka lose anything" when debugging duplicates.
Commit, then crash before the side effect finishes: if offset progress is committed before — or concurrently with, and faster than — the actual processing, a crash between the commit and the side effect means the record is skipped from the application's perspective on restart, even though Kafka itself did nothing wrong. This is the opposite risk, and it's why when you commit relative to when you finish processing is a correctness decision, not a performance one (see Production Engineering → Consumer Processing Patterns).
Groups don't share, groups compose
Two different groups subscribed to the same topic don't split the work — each group sees the full stream independently. This is what turns a Kafka topic into a broadcast primitive. One producer publishes orders; a warehouse group consumes it to trigger fulfillment, a fraud group consumes it to score transactions, an analytics group consumes it for the data warehouse. None of them coordinate. None of them slow each other down. This is the entire foundation of event-driven architecture.
The Complete Message Lifecycle
You've met every piece. Let's watch one message travel through all of them.
This section is the mental model to keep in your head whenever you're debugging Kafka behaviour. It's the answer to "what actually happens when I call producer.send()?" — end to end, from send() to commit().
The producer side
The application calls producer.send(record). The record is serialized to bytes using the configured key/value serializers. The producer's partitioner looks at the record's key (or, if no key, uses sticky/round-robin routing) and picks a partition — say partition 3. The record is added to a batch for that (topic, partition). If the batch is now full (batch.size reached) or the linger timer has fired (linger.ms elapsed since the batch started filling), the batch is compressed and sent to the leader broker for partition 3.
The broker side
The leader appends the batch to its partition-3 log at the next set of offsets. It also updates its log-end-offset for the partition. Concurrently, all followers for partition 3 have long-running fetch requests open — they now receive the new records and append them to their local logs, updating their own log-end-offsets. Once the ISR has replicated the batch (their LEOs are ≥ the batch's last offset), the leader advances the high-watermark — the offset up to which records are considered committed and visible to consumers.
With acks=all, only now does the leader acknowledge the producer. The producer receives the ack, marks the batch as complete, and moves on to the next batch. If the ack is lost in transit, the producer retries — and if idempotence is enabled, the broker deduplicates the retry using the producer's PID and sequence number.
The consumer side
Meanwhile, consumers in various groups have long-running fetch requests open for the partitions they're assigned. When the high-watermark advances, the broker responds to their pending fetches with the new records, delivered via zero-copy — bytes flow directly from the broker's page cache to the consumer's socket without any user-space copy. Each consumer processes the records in its application code and then commits progress (conventionally, the next offset to read) back to Kafka. On restart, the consumer resumes from that committed offset. Whether that means "exactly the records you'd expect" depends entirely on the order of processing and commit — process-then-commit can replay a record after a crash, commit-then-process can skip one; see Consumer Offsets above for both directions.
- Kafka is a distributed, replicated, append-only log — every property follows from those three words, and retention/compaction still govern how long records remain available.
- Topics are logical streams; partitions are the unit of parallelism and ordering; offsets identify records within a partition — not a global message id or timestamp.
- Producers push, consumers pull. Kafka never delivers — it makes data available, and consumers read at their own pace.
- Replication + ISR + `acks=all` + `min.insync.replicas` define a durability condition, not an unconditional guarantee — the leader is part of the ISR, and
acks=allonly waits for the current ISR, which can shrink. - Consumer groups assign each partition to at most one active member, which preserves ordering but does not by itself prevent duplicate processing around a crash near a commit boundary.
- Committed offsets conventionally mean "next offset to read", not "last offset successfully processed" — that distinction explains both reprocessing and skipped-record failure modes.
- Ordering is per-partition, not per-topic, and the key → partition mapping can change if partition count changes. Key your records if ordering matters for a business identity.
- Retention and compaction are independent of consumption. A slow consumer can fall off the end of the log and lose access to old records — alert on lag.
- Producer idempotence prevents duplicate log appends from supported retries; it does not make an external database write, API call or business command exactly-once.