Notes
What it is
Kafka is a distributed commit log. Producers append events to the end; consumers read from a position called an offset. Unlike a classic queue, reading does not delete anything. Events stay for a retention period, days or even forever, so a new service can start from the beginning and a broken one can rewind. Confluent and Amazon MSK run it for you, and Redpanda speaks the same protocol.
- a distributed commit log:
- producers append events,
- consumers read by offset
- events are kept (days, or
- forever), not deleted on read
- managed: Confluent, MSK,
- Aiven; compatible: Redpanda,
- WarpStream
Data model
The model: a topic, like orders, is split into partitions. Each partition is an ordered, append-only sequence of records, numbered by offset. A record has a key, a value, a timestamp and headers. The producer hashes the key to pick a partition, so all events for order nine eight one land in the same partition, in order. Remember this sentence: Kafka orders events within a partition, never across the whole topic.
topic: orders (3 partitions)
p0: [0][1][2][3][4][5] ...
p1: [0][1][2][3] ...
p2: [0][1][2][3][4] ...
record = key, value, timestamp,
headers
partition = hash(key) % 3
order is kept within a partition only Producing
Producers group records into batches per partition and send each batch to the broker that leads that partition. Waiting a few milliseconds to fill a batch, and compressing it, multiplies throughput. Records without a key are spread for balance, so they lose ordering. Idempotent producers are the default now: each batch carries a sequence number, so a retry after a timeout never writes a duplicate.
- key → partition (same key,
- same partition, same order)
- no key → spread for balance
- batch.size + linger.ms:
- group records, fewer requests
- compression: lz4 / zstd
- enable.idempotence=true:
- retries never duplicate
- (default since 3.0)
Why it is fast
Why is it so fast? Every write is a sequential append to a file, which even spinning disks do quickly. Recent data sits in the operating system's page cache, so consumers that keep up read from memory. Brokers send file data straight to the network socket without copying it through the application. Batching and compression shrink the work further, and partitions spread it across many brokers.
- • append-only: sequential disk
- writes, no random seeks
- • OS page cache holds recent
- data; most reads hit RAM
- • zero-copy: file → socket
- without passing through
- the application
- • batches + compression
- • partitions spread load
- across brokers
Replication
Each partition is replicated, usually three times. One broker is the leader and takes the writes; followers fetch from it. Replicas that are caught up form the in-sync set. With acks all and a minimum of two in-sync replicas, a write is confirmed only when at least two brokers have it, so losing any one broker loses nothing. If the leader dies, an in-sync follower takes over.
- replication.factor = 3
- leader takes writes, followers
- fetch from it
- ISR: in-sync replicas that
- are caught up
- acks=all + min.insync.
- replicas=2 → survives the
- loss of any one broker
- leader dies → an ISR member
- takes over
Consumer groups
Consumers join a group. Within a group, each partition is assigned to exactly one consumer, so the work is shared and per-key order is kept. Here two billing consumers split three partitions. A separate analytics group reads the same events independently, with its own offsets. One important consequence: partitions cap parallelism. Ten consumers on three partitions means seven sit idle, so choose the partition count with growth in mind.
- group = one logical reader
- each partition → exactly one
- consumer in the group
- more consumers than
- partitions → some sit idle
- each group tracks its own
- committed offsets
- billing and analytics read the
- same events independently
Offsets + delivery
A consumer records progress by committing its offset. Commit after processing and a crash means some events are processed again: that is at-least-once, the usual choice, so make handlers idempotent, as in Concepts episode 29. Commit first and a crash can skip events: at-most-once. Kafka transactions give exactly-once for read, process and write pipelines that stay inside Kafka, such as stream processing jobs.
- consumer commits its offset
- to an internal topic
- commit after processing
- → at-least-once (dupes on
- crash): make it idempotent
- commit before processing
- → at-most-once (may skip)
- exactly-once: transactions,
- for Kafka-to-Kafka pipelines
Retention
Retention is by time or size, seven days by default; old log segments are simply deleted. Or use log compaction, which keeps only the latest record for each key, so a topic becomes a durable table of current values, like each user's settings, that a new service can load from the start. Tiered storage moves older segments to object storage, so you can keep months cheaply.
- retention.ms (default 7 days)
- or retention.bytes per
- partition: old segments
- are deleted
- cleanup.policy=compact:
- keep the latest record per
- key forever (a changelog,
- e.g. user settings)
- tiered storage: old data in S3
Rebalancing + KRaft
Two operational points. When a consumer joins, leaves or stops responding, the group rebalances and partitions move; newer cooperative protocols move only what they must, instead of pausing everyone. And cluster metadata is now kept by a small Raft quorum of controllers called KRaft; ZooKeeper support was removed in Kafka four. Also note that adding partitions later changes which partition a key maps to, which breaks per-key order for a moment.
- rebalance: consumer joins or
- leaves → partitions are
- reassigned; cooperative
- protocol avoids full pauses
- KRaft: Raft-based controller
- quorum stores metadata;
- ZooKeeper removed in 4.0
- adding partitions changes
- key → partition mapping
Great at
Kafka is great at decoupling: the order service publishes one event and billing, email, search and analytics each consume it without knowing about each other. It handles huge volumes of clickstream, logs and metrics. It is the standard pipe for change data capture from databases. It feeds stream processors like Flink. And because history is kept, you can replay events to rebuild a cache or fix a bug's damage.
- • decoupling services: one
- event, many subscribers
- • activity, clickstream, logs,
- metrics at huge volume
- • change data capture
- (Debezium → Kafka)
- • stream processing (Flink,
- Kafka Streams)
- • replay to rebuild state
Where it hurts
Where it hurts. It is not a classic task queue: no per-message acknowledgement, no delayed delivery, no priorities, and retries mean building retry topics yourself. Newer share groups add queue-like consumption, but SQS or RabbitMQ are simpler for jobs. Order exists only within a partition, and one slow event blocks everything behind it in that partition. A hot key overloads one partition. Running clusters yourself is real work.
- • no per-message ack, delay,
- priority or easy retries
- (classic queues do this)
- • order only per partition
- • parallelism capped by
- partition count
- • one slow event blocks its
- partition (head of line)
- • hot key → hot partition
- • heavy to run yourself
Use it when
Use Kafka when several services must react to the same stream, when volume is high and order per key matters, or when you need replay and change data capture. Avoid it for simple background jobs that need retries, delays and per-message acknowledgement, where SQS or RabbitMQ fit better, for low volume where it is overkill, and for request and response calls. Concepts episode 28 compares queues and pub-sub.
- USE when
- many services react to one
- event stream
- high volume, ordered by key
- you need replay or CDC
- AVOID when
- simple background jobs with
- retries/delays → SQS, Rabbit
- low volume, small team
- request/response calls
Numbers
Numbers to have ready. A well-tuned broker sustains hundreds of megabytes a second, and end-to-end latency is typically a few milliseconds to a few tens of milliseconds, depending on batching. Use a replication factor of three with a minimum of two in-sync replicas. Default retention is seven days and the default maximum message size is about one megabyte, so put big payloads in object storage and send a pointer.
throughput per broker 100s of MB/s
end-to-end latency ~5-50 ms
replication.factor 3
min.insync.replicas 2
default retention 7 days
default max message ~1 MB
partitions per cluster up to ~100k+
(with KRaft)
consumers per partition 1 per group Say it like this
How to say it. The order service writes the order and publishes an order placed event through the outbox pattern, from Concepts episode 33, so the database and Kafka never disagree. The key is order id, so each order's events stay in sequence. Twenty-four partitions for headroom, replication factor three, acks all. Billing and email are separate consumer groups, and their handlers are idempotent because delivery is at least once.
- "Order service writes the
- order, then publishes
- OrderPlaced to Kafka via the
- outbox. Key = order_id so
- each order’s events stay in
- order. 24 partitions, RF 3,
- acks=all. Billing and email
- are separate groups;
- handlers are idempotent."