Loading...
Loading...
Append-only logs, consumer groups, and high-throughput streaming
HDFS just rebuilt its namespace by replaying 190,000 journal edits in order onto the last snapshot. That recovery trick, an append-only log as the source of truth, also works as transport. Your orders service emits payment succeeded, and fraud, warehouse, and analytics all need it at different speeds, while analytics also needs to replay last month after a bugfix. A routing queue deletes after one consumer, so the second team finds nothing. A log, which is an append-only sequence of records kept on disk that readers replay by bookmark, keeps ordered records and lets each consumer track its own offset, which is its position in the sequence, independently. Think of a shared notebook with numbered pages as the single analogy here: writers only append new numbered pages, and every reader keeps their own bookmark, so a slow reader never blocks a fast one and anyone can reread page one.
The naive fix is to fan out with one queue per team, copying every event three ways. That works for three teams and collapses at thirty, because every new reader multiplies storage, routing, and operational surface: thirty copies of a 4.8-terabyte topic burn 144 terabytes where one log holds 4.8, and each copy needs its own routing rules and retention math. The real solution is one durable log with independent bookmarks per reader, trading broker-side routing intelligence for reader-side simplicity at far higher throughput.
Topic "payments" (3 partitions)
p0: [offset 0: {...}][1: {...}][2: {...}] -> replicas on broker 1,2,3
p1: [0: {...}][1: {...}] -> replicas
Producer chooses partition by key hash (order-key) or round-robin if no keyA partition, which is one ordered section of a topic, guarantees order only inside itself, never across partitions. If order matters for one user timeline, key every event by user id so all events for one user hash to the same partition. Scaling a topic means adding partitions, but you cannot reduce them later and re-keying changes which records land where.
A consumer group, which is a set of workers sharing one logical subscription, splits partitions among its members: a group named fraud-detectors with 3 workers over 3 partitions gives each worker one partition. Adding a fourth worker does nothing until partitions increase, because the maximum useful workers in one group equals the partition count. On join or leave, the group coordinator, which is the broker process assigning partitions to members, rebalances, which is reassigning partitions, using range, round-robin, or cooperative sticky assignment that moves incrementally instead of stopping everything.
The broker retains records per retention time, keeping the notebook pages for days regardless of who read them. Each consumer commits its offset, which is the next position to read, to an internal offsets topic. Automatic commit before processing duplicates on crash, so prefer manual commit after handling completes.
consumer.process(record){
await handle(record);
await consumer.commit(record.offset+1); // exactly at record boundary
}In-sync replicas, which are follower copies confirmed caught up with the leader, must acknowledge for durable writes. Acknowledging on leader receipt alone is fast but lossy if the leader crashes before replicating, while requiring all in-sync replicas is durable but slower. An idempotent producer, which is a sender the broker deduplicates by sequence number, plus transactions gives effectively-once delivery without duplicates on retry.
| Feature | Kafka log | RabbitMQ queue |
|---|---|---|
| Retention | Days to weeks with replay by bookmark, because pages persist by time | Deleted after acknowledgment, because delivery ends the broker's job |
| Routing | Consumer decides what each record means; the broker stays simple | Exchanges and bindings route per message; the broker stays clever |
| Ordering | Order within each partition only | Order per queue with a single consumer only |
| Throughput | Millions per second via sequential disk writes and zero-copy reads, which send bytes without copying through user memory | Tens of thousands per second with routing work per message |
| Replay | Yes, by rewinding the bookmark to an earlier offset | No, acknowledged work is gone |
Too few partitions means one broker goes hot with no room to spread load, while too many means thousands of open file handles and slow rebalances. Start 6 to 12 per topic and benchmark producer throughput per partition at roughly 10 to 50 megabytes per second sustained.
Watch in-sync replica shrink, which is followers falling behind, plus under-replicated partitions, consumer lag, which is how far the reader bookmark trails the newest page, and broker request queues. Alert on lag past twice your slowest realistic processing time rather than on raw depth alone.
An idempotent producer plus a transactional id plus consumers reading only committed data gives read-process-write effectively once. It costs latency and operational care, so enable it only where duplicates truly break correctness such as money movement.
Your orders topic takes 8 megabytes per second at peak. With replication factor 3, which is storing 3 copies of each record, that is 24 megabytes per second of disk write cluster-wide, and 7-day retention, which is 604,800 seconds, means 8 times 604,800, about 4.8 terabytes of logical data, times 3 copies divided across 3 brokers, about 4.8 terabytes per broker for this topic alone. Teams that skip this multiplication discover retention filling disks the week of launch, and then learn that deleting log data early breaks the replay promise downstream teams already depend on.
disk per broker ~= ingress_MB_s x retention_s x RF / brokers
8 MB/s x 604,800 s x 3 / 3 = ~4.8 TB per broker (this topic alone)
throughput ceiling per partition: ~10-50 MB/s sustained
need 200 MB/s? -> at least 6-8 partitions on distinct brokers
max consumers in one group = partition count (extra consumers idle)
sane production start:
partitions: 12, RF: 3, min.insync.replicas: 2
acks: all, enable.idempotence: true
retention.ms: 604800000 (7d), compression: lz4
unclean.leader.election.enable: falseA consumer takes 6 minutes to process a batch while the maximum poll interval, which is how long the broker waits before declaring a worker dead, is 5 minutes. The broker declares it dead, revokes its partitions, and hands them to a survivor, which replays uncommitted offsets and also takes 6 minutes. The group rebalances in a loop with throughput near zero, and every deploy or long garbage-collection pause retriggers it. Restarting faster never breaks this loop, because the batch still exceeds the deadline on every attempt.
Logs remember everything, which raises the obvious question: what if you never threw anything away at all, and rebuilt the present by replaying the past? Some systems do exactly that. It is called event sourcing.