Message Queues and Streaming: Decoupling at Scale

Queues vs logs, Kafka vs RabbitMQ vs SQS, delivery semantics, partitioning, consumer groups, and when streaming beats request-response.

2.8advanced 35 min 5,249 words Updated 2026-08-06

TL;DR: Message systems split into two models, not two products. A queue delivers each message to one consumer and deletes it after acknowledgement. A log (or stream) is an append-only, ordered, durable record that many consumer groups read independently at their own pace. Kafka, Kinesis and Pulsar are log-first; SQS is queue-only; RabbitMQ ships both, since streams give it "non-destructive consumer semantics" alongside its classic and quorum queues[1]. Jay Kreps's 2013 essay reframed the log as the source of truth from which every downstream system derives its state[2]. The practical delivery guarantee is at-least-once with idempotent consumers; exactly-once holds "when reading, processing and writing data on Kafka topics," and reaching other systems "generally requires cooperation with such systems"[3]. As of October 2019, LinkedIn processed over 7 trillion messages per day across 4,000+ brokers[4], and Netflix processes hundreds of billions of events daily through its Keystone pipeline. The partition is the unit of parallelism, the boundary of ordering, and the cap on consumer-group scaling (though not on share groups).

Learning Objectives#

After this module, you will be able to:

  • Distinguish a queue from a log and pick the right one for a workload
  • Design for at-least-once delivery with idempotent consumers
  • Reason about Kafka partitions, consumer groups, and ordering guarantees
  • Handle backpressure, dead-letter queues, and poison messages
  • Compare Kafka, RabbitMQ, SQS, Kinesis, and Pulsar on real criteria
  • Explain why partition count is a permanent architectural decision

Intuition#

Think of two services at the post office.

The first is the package counter. You hand a parcel to a clerk. The clerk gives it to exactly one delivery driver. Once delivered, the receipt is shredded. If you want to send the same parcel to two people, you need two parcels. This is a queue: one message, one consumer, gone after processing.

The second is the newspaper archive. Every edition is printed, numbered, and shelved in order. Any subscriber can walk in, find edition #4,217, and read forward from there. A new subscriber does not need to wait for tomorrow's paper; they can start from any past edition. Adding a subscriber does not slow down existing readers. This is a log: append-only, ordered, replayable, multi-subscriber.

The queue is simpler. The log is more powerful. Most confusion in system design comes from reaching for a queue when you need a log, or paying the operational cost of a log when a queue would suffice.

The rest of this chapter teaches you to tell the difference and pick correctly.

Theory#

Queue vs stream: the fundamental distinction#

A queue (SQS, a RabbitMQ classic or quorum queue) tracks per-message state: visible, in-flight, acknowledged. Competing consumers drain work faster than one could alone. Once acknowledged, the message is gone. If you add a new consumer service next month, it starts from "now" with no history[5].

A log (Kafka, Kinesis, Pulsar, RabbitMQ streams) stores durable, offset-addressable segments. Each consumer group tracks its own offset. Adding a new consumer group replays history from offset 0 without disturbing existing consumers. The log is not just a transport mechanism; it is the source of truth that downstream systems (search indexes, caches, warehouses, microservices) derive their state from[2:1].

Pick the model first, then the product. Several brokers implement both, so "does this product support replay?" is the wrong question; "which queue type am I declaring?" is the right one.

Yes No Yes No Yes No New async workload Need replayor multi-subscriber? Use a log:Kafka / Kinesis / Pulsaror RabbitMQ streams Rich routingfanout / topic? RabbitMQ In AWSand low-ops? SQS Standardor FIFO if ordered Redis Streamsor NATS JetStream

A pragmatic decision tree: start with replay and fan-out needs, then narrow by routing complexity and operational appetite.

Use a log when you need replay, multi-subscriber fan-out, or CDC. Use a queue when you need simple work distribution with per-message acks and no history.

Delivery guarantees: at-most, at-least, exactly-once#

Three levels exist, and only one is honest at scale:

  • At-most-once (Kafka acks=0, fire-and-forget): the producer sends and moves on. Messages can be lost on broker crashes.
  • At-least-once (the practical default): the producer retries until it gets an ack, and the consumer may see the same record twice. Be precise about where the duplicate comes from. enable.idempotence defaults to true, so the broker dedupes producer retries by PID and sequence number[6]; producer retries are not your duplicate source on a modern client. The duplicate comes from the consumer side: a consumer that processes records and then crashes before committing its offset will reprocess them after rebalance, which Kafka's own docs describe as the at-least-once case[3:1]. Consumers must therefore be idempotent. Netflix's Keystone pipeline uses acks=1 (leader-only acknowledgement), a pragmatic middle ground that accepts a small data loss risk on leader failure in exchange for lower latency and higher availability[7].
  • Exactly-once (Kafka EOS): requires enable.idempotence=true (producer sequence numbers deduped broker-side), a transactional.id (atomic writes across partitions plus consumer offset commit in the same transaction), and consumers using isolation.level=read_committed[8]. The often-quoted "3% overhead" needs its baseline: Confluent measured 1 KB messages and 100 ms transactions declining "only by 3%, compared to the throughput of a producer configured for at least once, in-order delivery (acks=all, max.in.flight.requests.per.connection=1)", and "by 20% compared to ... at most once delivery with no ordering guarantees (acks=1, max.in.flight.requests.per.connection=5)", which was the default at the time[8:1]. Cite the pairing, not the 3% alone.
Important

Kafka's exactly-once is scoped, not brittle. The docs say the transactional producer plus a read_committed consumer provide exactly-once delivery "when reading, processing and writing data on Kafka topics," and that "exactly-once delivery for other destination systems generally requires cooperation with such systems, but Kafka provides the primitives which makes implementing this feasible"[3:2]. An external RPC or database write does not break the chain; it moves the burden to the far side. Cooperation means storing the consumer offset in the same place as the output, so data and offset commit or fail together, which is exactly what Kafka Connect's HDFS connector does. Where the destination cannot cooperate, fall back to at-least-once plus an application-level idempotency key.

Kafka architecture: topics, partitions, ISR, KRaft#

A Kafka topic is split into N partitions. Each partition is a replicated log with one leader and several followers. The ISR (in-sync replicas) is the subset of replicas that have caught up to the leader.

Key invariants:

  • acks=all + min.insync.replicas=2 on replication factor 3 guarantees no data loss under single-broker failure[6:1]. It also costs availability, which the durability framing hides: if acks=all and the ISR falls below min.insync.replicas, "the producer will raise an exception (either NotEnoughReplicas or NotEnoughReplicasAfterAppend)"[9]. On RF=3 with min.insync.replicas=3, losing one broker stops writes to that partition entirely. Raising the setting buys durability by spending availability; 2-of-3 is the usual compromise for that reason.
  • Producer idempotence (default since Kafka 3.0) assigns each producer a PID and each batch a sequence number; brokers dedupe retries with max.in.flight.requests.per.connection <= 5[6:2].
  • Partition throughput: 10-50 MB/s per partition in production[10].
  • Adding partitions is not a free scale-out. Kafka's operations docs are explicit: "adding partitions doesn't change the partitioning of existing data ... if data is partitioned by hash(key) % number_of_partitions then this partitioning will potentially be shuffled by adding partitions but Kafka will not attempt to automatically redistribute data in any way"[9:1]. A key's future records land on a new partition while its history stays put, so the per-key ordering guarantee is broken across the change and two consumers can process one key's stream concurrently. Over-provision at creation, or plan a topic migration rather than an --alter.
Producers Kafka Cluster (RF=3) topic: orders Consumer Group: billing Consumer Group: search-index key=order_id key=order_id after N retries Order Service Payment Service orders.DLQ Partition 0Leader: B1ISR: B1,B2,B3 Partition 1Leader: B2ISR: B2,B3 Partition 2Leader: B3ISR: B1,B3 billing-1 billing-2 indexer-1

Producers write keyed events into partitioned topics; two consumer groups read independently at their own offsets, and poison messages route to a DLQ after N retries.

KRaft (KIP-500) replaced ZooKeeper with an internal Raft-based metadata quorum. The rollout took years: KIP-833 marked KRaft "production ready for new clusters only" in Kafka 3.3 (announced 3 October 2022), 3.5 was the bridge release that allowed ZooKeeper-to-KRaft migration and deprecated ZooKeeper, and Kafka 4.0 (18 March 2025) is "the first major release to operate entirely without Apache ZooKeeper"[11][12][13]. This eliminates an entire system from the operational footprint and enables faster controller failover.

Tiered storage (KIP-405, GA in Kafka 3.9) offloads older log segments to object storage (S3, GCS), significantly reducing storage cost for long-retention topics while keeping recent data on local disks for hot reads[14][15].

RabbitMQ and AMQP: exchanges, flexibility, routing#

RabbitMQ implements AMQP 0-9-1. Producers publish to an exchange; bindings attach queues to exchanges with a routing key pattern[16]:

  • Direct exchange: routes to queues whose binding key exactly matches the routing key.
  • Fanout exchange: ignores routing keys, delivers to every bound queue.
  • Topic exchange: uses dotted routing keys (orders.eu.paid) with * (one word) and # (zero or more words) wildcards.
routing_key: orders.eu.paid binding: orders.eu.* binding: orders.*.paid binding: orders.# Order Service Topic Exchange(orders) queue: eu-billing queue: revenue-analytics queue: audit EU Billing Worker Analytics Worker Audit Worker

A topic exchange routes by wildcard pattern; one message fans out to multiple queues without duplication logic in the producer.

Modern RabbitMQ (3.8+) provides quorum queues built on Raft, replacing the deprecated mirrored-queue feature with clear failure semantics[17]. Throughput per queue (tens of thousands msgs/sec) is an order of magnitude below Kafka's per-partition figures[18].

What RabbitMQ does not lack is replay. Since 3.9 it also ships streams, which the docs describe as "an append-only log of messages that can be repeatedly read until they expire," with explicitly "non-destructive consumer semantics": consumers attach at any point in the log, read the same messages as many times as they want, and track their own offset[1:1]. Streams were built for large fan-outs, time-travel replay, high throughput, and large backlogs, the four things classic queues handle badly[1:2].

The honest framing is therefore queue versus stream as models, not RabbitMQ versus Kafka as products. A classic or quorum queue has destructive consume: once acknowledged, the message is gone and a consumer added next month starts from now. A stream does not. Both models are available in RabbitMQ, and streams are declared per queue with x-queue-type: stream[1:3].

Use RabbitMQ when you need rich routing (topic/fanout/headers), low-latency task distribution, or RPC-style request/reply patterns. Use Kafka when you need a durable, replayable event log.

SQS, SNS, Kinesis, Pulsar, and Redpanda#

SystemModelThroughputOrderingOps burdenBest for
SQS StandardQueueUnlimitedNoneZero (managed)Async jobs in AWS
SQS FIFOQueue300 TPS (3K batched, 70K high-throughput)[19]Per MessageGroupIdZeroOrdered work in AWS
KinesisLog1 MB/s or 1K records/s per shard[20]Per shardLow (managed)AWS-native streaming
PulsarLogHigh (BookKeeper)Per partitionMedium (brokers + bookies)Multi-tenant, geo-replicated
RedpandaLogKafka-compatible, C++, no JVMPer partitionLow (single binary)Low-latency, edge

SQS uses a visibility timeout (default 30s): after ReceiveMessage, the message is invisible to other consumers. If the consumer crashes before DeleteMessage, the message reappears. A redrive policy moves messages to a DLQ after maxReceiveCount failed receives[21].

For fan-out in AWS, combine SNS (pub/sub) with SQS (per-subscriber queue): SNS delivers to N SQS queues, each consumed independently. This gives you log-like multi-subscriber semantics without running Kafka.

Consumer patterns: groups, competing consumers, DLQ, backpressure#

Consumer groups distribute partitions across members, and one partition goes to exactly one member. A consumer group therefore cannot have more active consumers than partitions, which is the classic ceiling on parallelism[10:1]. That ceiling is now conditional on the group type. Share groups (KIP-932, "Queues for Kafka") went production-ready in Kafka 4.2, and there "partitions may be assigned to multiple consumers" and "the number of consumers in a share group can exceed the number of partitions in a topic"[3:3][22]. Records are acquired under a time-limited lock (share.record.lock.duration.ms, 30 s by default) and acknowledged individually, so you trade per-partition ordering for queue-like parallelism. If your work is genuinely order-independent, share groups remove the partition-count ceiling; if you need per-key ordering, the ceiling still applies.

Rebalancing has moved on twice. KIP-429 cooperative rebalancing (Kafka 2.4+) narrowed the older "stop-the-world" protocol so only partitions that need to move are revoked[23][24]. The current answer is KIP-848, the next-generation consumer rebalance protocol, Generally Available since Kafka 4.0. It is "fully incremental" and "no longer relies on a global synchronization barrier," heartbeat interval and session timeout are now server configs, and the assignor itself moved server-side (group.consumer.assignors, defaulting to uniform and range). Groups on the new protocol are called Consumer groups; the old ones are Classic. On the client you must opt in with group.protocol=consumer[13:1][25].

Backpressure is managed via bounded in-flight windows:

  • Kafka: max.poll.records and fetch.max.bytes
  • SQS: approximately 120,000 in-flight messages per standard queue, "depending on queue traffic and message backlog"; past it, short polling returns OverLimit[26]
  • RabbitMQ: prefetch (QoS) count

Dead Letter Queue (DLQ): after N retries with exponential backoff, produce the failing record to a <topic>.DLQ, commit the offset, and move on. This is the Dead Letter Channel pattern from Hohpe and Woolf's Enterprise Integration Patterns[27]. Alert on DLQ depth. Build a redrive tool to re-inject fixed messages.

Lag monitoring: the difference between log-end offset and committed offset. LinkedIn's Burrow is the canonical consumer-lag monitor[28].

alt [Key is new] [Key exists (duplicate)] send(order_id=42) seq=7 timeout (no ack) retry send(order_id=42) seq=7 ack offset=1001 poll() offset=1001, order_id=42 INSERT idempotency_key=42 OK Process side effect commit offset=1002 Duplicate commit offset=1002 (skip work) Idempotent dedupe by PID+seq Producer Kafka Broker Consumer Idempotency Store

The producer retry creates a duplicate at the broker; the idempotent producer dedupes by PID+sequence, and the consumer deduplicates using a persisted idempotency key before committing the offset.

Real-World Example#

LinkedIn Kafka: 7 trillion messages per day (as of October 2019).

LinkedIn is where Kafka was born, and it remains the largest deployment with published numbers. Every figure below is from a single post dated 8 October 2019, and LinkedIn has not refreshed them since: over 100 Kafka clusters with 4,000+ brokers, 100,000+ topics, 7 million partitions, and more than 7 trillion messages per day[4:1]. Treat them as a 2019 snapshot, not a current reading. The post does not name a largest cluster; it says "some larger clusters have more than 140 brokers and host one million replicas in a single cluster"[4:2].

Every LinkedIn service runs a Kafka client (or a REST proxy for non-JVM languages). Events flow to regional Kafka clusters and are mirrored cross-region by Brooklin, which replaced Kafka MirrorMaker in 2018; as of 11 April 2022, LinkedIn reported mirroring "more than seven trillion messages per day between the clusters using Brooklin"[29]. Downstream they are consumed by Samza stream processors, Hadoop, Pinot, and hundreds of microservices.

Key engineering decisions that kept this running:

  • Maintenance mode brokers: brokers flagged for decommission stop receiving new partition assignments, letting SREs safely drain hardware without triggering rebalance storms[4:3].
  • Controller memory optimization: reusing UpdateMetadataRequest objects to prevent cascading controller failures in clusters with millions of replicas[4:4].
  • Cruise Control: automated partition rebalancing and self-healing across clusters.

The failures are instructive. LinkedIn's KIPs document production issues: KIP-291 (separating controller from data-plane connections to avoid head-of-line blocking), KIP-354 (maximum log compaction lag, after a compaction stall filled disks), and KIP-380 (detecting outdated control requests after broker bounces)[4:5].

The lesson: at that volume, every configuration choice (partition count, acks setting, retention policy, rebalance strategy) is the difference between a healthy pipeline and a 3am page.

Trade-offs#

SystemThroughputOrderingReplayOps burdenCost modelOur Pick
Kafka10-50 MB/s per partitionPer partitionFull log replayHigh (JVM, KRaft, disks)Infra + teamOrdered event streams, CDC, analytics
SQS StandardUnlimitedNoneNoZeroPay-per-requestAsync jobs in AWS, no ordering needed
SQS FIFOUp to 70K TPSPer MessageGroupIdNoZeroPay-per-requestOrdered work queues in AWS
RabbitMQTens of K/s per queuePer queueQueues no; streams yes[1:4]Medium (Erlang, quorum queues)InfraRich routing, RPC, task distribution
PulsarHigh (BookKeeper)Per partitionFull replayMedium-High (brokers + bookies)InfraMulti-tenant SaaS, geo-replication
Kinesis1 MB/s per shardPer shardUp to 365-day replayLow (managed)Per-shard-hourAWS-native streaming, small teams

Decision rule: Use Kafka for partitioned ordered streams with replay. Use SQS when you do not need ordering or replay and want zero ops. Use RabbitMQ when you need flexible routing patterns. Use Kinesis or Pulsar when your constraints (AWS-native or multi-tenant) demand them.

Common Pitfalls#

Warning

Hot partitions. If your partition key has skewed cardinality (one customer generates 80% of traffic), one partition gets all the load while others idle. Monitor per-partition lag. Use composite keys (customer_id + order_date) or a sub-partitioning strategy to spread hot keys.

Warning

Stop-the-world rebalance. Under the legacy "eager" protocol, any consumer that misses a heartbeat (GC pause, slow processing exceeding max.poll.interval.ms) triggers every consumer to surrender every partition. On Kafka 4.0 or later, move to the KIP-848 protocol (group.protocol=consumer), which is fully incremental[13:2]. If you are still on classic groups, set CooperativeStickyAssignor and static membership (group.instance.id) so pod restarts during deploys do not trigger rebalance[23:1].

Warning

Poison messages without a DLQ. A single malformed message stalls an entire partition. Consumers crash on deserialization, retry, crash again, repeat forever. Implement the Dead Letter Channel pattern: after N retries with exponential backoff, produce to a DLQ, commit the offset, and alert[27:1][30].

Warning

Unbounded retention without backpressure. Producers burst; consumers cannot keep up; lag grows to hours; retention expires and data is lost before consumption. Monitor lag with Burrow[28:1]. Autoscale consumers up to partition count. Alert when lag exceeds consumption_rate * X minutes.

Warning

Using Kafka for RPC-style request/response. Kafka is optimized for high-throughput, high-latency streaming. Request/response patterns need low-latency, per-message routing, and correlation IDs. Use RabbitMQ or gRPC for synchronous communication. Kafka adds unnecessary latency (batching, polling intervals) and complexity (reply topics, correlation headers) for this pattern.

Warning

Ignoring consumer lag until retention expires. Consumer lag is not a warning; it is a countdown. Once lag exceeds retention.ms, messages are deleted before consumption. There is no recovery. Set alerts at 50% of retention, not 90%.

Exercise#

Design Challenge: You are building the event backbone for a fintech platform. Requirements: 100 million events per day, 3 consumer types (fraud detection with sub-second latency, billing with exactly-once semantics, analytics warehouse with hourly batch loads), and strict per-user ordering of financial events.

Hint

The partition key determines ordering scope. Per-user ordering means user_id as the partition key. But how many partitions do you need for 100M/day throughput? And how do you serve three consumers with different latency and semantics requirements from the same topic?

Solution

Throughput sizing: 100M events/day is roughly 1,157 events/sec average, with 3-5x peak bursts (3,500-5,800/sec). At ~1 KB per event, that is 3-6 MB/s peak. A single Kafka partition handles 10-50 MB/s, so throughput alone needs only 1 partition. But parallelism matters more.

Partition count: you need enough partitions to parallelize consumers. Fraud detection needs low latency, so give it dedicated consumer instances. With 3 consumer types and future scaling, start with 32 partitions. This allows up to 32 parallel consumers per group.

Partition key: user_id. This guarantees all events for a user land on the same partition, preserving per-user ordering. With millions of users, distribution across 32 partitions will be roughly uniform.

Three consumer groups on one topic:

  1. Fraud detection (consumer group fraud): reads with max.poll.records=1 for lowest latency. Processes inline. No batching.
  2. Billing (consumer group billing): uses Kafka transactions. Reads events, writes billing records to a billing topic, and commits consumer offsets atomically. For the external DB write, uses an idempotency key (event_id) in the billing database.
  3. Analytics (consumer group analytics): reads in large batches, buffers in memory, flushes to S3/warehouse hourly. Tolerates lag.

DLQ strategy: each consumer group has its own DLQ topic (events.fraud.DLQ, events.billing.DLQ). After 3 retries with exponential backoff, produce to DLQ and commit offset. Alert on DLQ depth > 0.

Exactly-once for billing: Kafka transactional producer wraps the read-process-write cycle. For the external database write, the billing service uses INSERT ... ON CONFLICT DO NOTHING with event_id as the idempotency key. This makes the consumer idempotent regardless of Kafka-level duplicates.

Key Takeaways#

  • A queue delivers each message to one consumer and deletes it. A log is an append-only, replayable record that many consumer groups read independently. These are models, not products: RabbitMQ ships both.
  • At-least-once with idempotent consumers is the honest, practical delivery guarantee. Kafka's exactly-once holds for reading, processing and writing on Kafka topics; other destinations need cooperation (offset stored with the output) rather than being impossible.
  • The partition is the unit of parallelism and the boundary of ordering. A consumer group cannot have more active consumers than partitions; a share group (KIP-932, production-ready in Kafka 4.2) can, at the cost of per-partition ordering.
  • Ordering is only meaningful per partition. Design your partition key for the ordering you need (typically entity ID).
  • Partition count is nearly permanent. Increasing it later reshuffles hash(key) % N, and Kafka "will not attempt to automatically redistribute data in any way," so a key's history and its future live on different partitions and per-key ordering breaks across the change. Over-provision at creation.
  • DLQs are not optional. Poison messages will happen, and without a DLQ they block the entire partition indefinitely.
  • Use Kafka for ordered event streams with replay. Use SQS for zero-ops async jobs. Use RabbitMQ for rich routing and task distribution.

Further Reading#

Flashcards#

QWhat is the fundamental difference between a queue and a log?

AA queue delivers each message to one consumer and deletes it after acknowledgement (destructive consume). A log is an append-only, offset-addressable sequence that many consumer groups can read independently and replay from any point (non-destructive consume). They are models rather than product categories: RabbitMQ implements both.

QWhat is the honest delivery guarantee for systems that write to external databases?

AAt-least-once delivery with idempotent consumers. Kafka's exactly-once guarantee is defined for reading, processing and writing data on Kafka topics; reaching another system exactly once requires that system's cooperation, typically by storing the consumer offset alongside the output so both commit or neither does.

QWhy is partition count nearly permanent in Kafka?

ABecause the default partitioner uses `murmur2(key) % N`. Adding partitions changes N, so a key's new records route to a different partition while Kafka leaves its existing data where it is. Per-key ordering is broken across the change and one key's stream can be processed by two consumers at once.

QWhat is the ISR and why does `min.insync.replicas=2` matter?

AThe ISR (in-sync replicas) is the set of replicas caught up to the leader. With `acks=all` and `min.insync.replicas=2` on replication factor 3, at least two replicas must acknowledge before the write is committed, guaranteeing no data loss under single-broker failure. The cost is availability: if the ISR drops below `min.insync.replicas`, producers get `NotEnoughReplicas` and writes to that partition stop. That is why 2-of-3 rather than 3-of-3 is the standard setting.

QHow does cooperative rebalance differ from eager rebalance, and what superseded both?

AEager rebalance (legacy) revokes all partitions from all consumers on any membership change, causing a stop-the-world pause. Cooperative rebalance (KIP-429, Kafka 2.4+) only revokes partitions that need to move. KIP-848, GA since Kafka 4.0, replaces both: it is fully incremental with no global synchronization barrier, and the assignor runs server-side. Clients opt in with `group.protocol=consumer`.

QWhat is the Dead Letter Queue pattern?

AAfter N retries with exponential backoff, produce the failing record to a dedicated DLQ topic, commit the offset, and move on. This prevents a single poison message from blocking an entire partition. Alert on DLQ depth and build a redrive tool.

QWhen should you use RabbitMQ instead of Kafka?

AWhen you need rich routing (topic/fanout/headers exchanges), low-latency task distribution, or RPC-style request/reply patterns. Note that "no replay" is a property of RabbitMQ's classic and quorum *queues*, not of RabbitMQ: streams give it an append-only log with non-destructive consume and attach-at-any-offset. Kafka still wins on per-partition throughput and ecosystem.

QWhat is the maximum consumer parallelism for a Kafka topic with 32 partitions?

A32 for a consumer group: one partition goes to one member, and extra consumers sit idle. A share group is not bound by this, since partitions can be assigned to multiple consumers and the group can exceed the partition count, but you give up per-partition ordering to get it.

QHow does SQS FIFO ordering work?

ASQS FIFO preserves order within a `MessageGroupId`. Messages with the same group ID are delivered in order. Different group IDs are processed in parallel, giving horizontal scaling of ordered work.

QWhat throughput overhead does Kafka's exactly-once (transactional producer) add?

AIt depends entirely on the baseline. Confluent's benchmark (1 KB messages, 100 ms transactions) measured a 3% decline against at-least-once *in-order* delivery (`acks=all`, `max.in.flight=1`) and a 20% decline against at-most-once with no ordering (`acks=1`, `max.in.flight=5`), the default of the day. Citing 3% without the baseline overstates the case.

QNetflix Keystone uses `acks=1` instead of `acks=all`. What is the trade-off?

A`acks=1` means only the leader confirms the write. If the leader crashes before followers replicate, data is lost. Netflix accepts this small data loss risk in exchange for lower latency and higher availability.

QWhat is consumer lag and why is it dangerous?

ALag is the difference between the log-end offset and the consumer's committed offset. If lag grows beyond `retention.ms`, messages are deleted before consumption with no recovery possible.

QHow does LinkedIn handle 7 trillion messages per day?

AAs of October 2019: 100+ Kafka clusters, 4,000+ brokers, 7 million partitions. Key practices: maintenance-mode brokers for safe decommission, Cruise Control for automated rebalancing, Brooklin for cross-cluster mirroring (which replaced MirrorMaker in 2018), and controller memory optimizations to prevent cascading failures in clusters with a million replicas.

QWhat problem does tiered storage (KIP-405) solve?

AIt offloads older log segments to object storage (S3/GCS), significantly reducing storage cost for long-retention topics while keeping recent data on local disks for low-latency reads.

QWhen should you use SQS over Kafka?

AWhen you need zero operational burden, pay-per-request pricing, and do not need ordering, replay, or multi-subscriber fan-out. SQS is the right choice for simple async job processing in AWS.

References#

  1. RabbitMQ, "Streams and Superstreams". https://www.rabbitmq.com/docs/streams ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. Jay Kreps, "The Log: What every software engineer should know about real-time data's unifying abstraction", LinkedIn Engineering, 2013. The original URL now returns HTTP 404; cited via the Internet Archive. https://web.archive.org/web/20240105095933/https://engineering.linkedin.com/distributed-systems/log-what-every-software-engineer-should-know-about-real-time-datas-unifying ↩︎ ↩︎

  3. Apache Kafka documentation, "Design". https://kafka.apache.org/documentation/#design ↩︎ ↩︎ ↩︎ ↩︎

  4. Jon Lee, Wesley Wu, "How LinkedIn customizes Apache Kafka for 7 trillion messages per day", LinkedIn Engineering, October 8, 2019 ("some larger clusters have more than 140 brokers and host one million replicas in a single cluster"). https://www.linkedin.com/blog/engineering/open-source/apache-kafka-trillion-messages ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  5. AWS, "Amazon SQS queue types". https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-queue-types.html ↩︎

  6. Apache Kafka project, ProducerConfig.java source, clients module. https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/clients/producer/ProducerConfig.java ↩︎ ↩︎ ↩︎

  7. Real-Time Data Infrastructure Team, "Kafka Inside Keystone Pipeline", Netflix Tech Blog, April 2016. https://netflixtechblog.com/kafka-inside-keystone-pipeline-dd5aeabaf6bb ↩︎

  8. Neha Narkhede, Guozhang Wang, "Exactly-Once Semantics Are Possible: Here's How Kafka Does It", Confluent Blog, 2017. https://www.confluent.io/blog/exactly-once-semantics-are-possible-heres-how-apache-kafka-does-it/ ↩︎ ↩︎

  9. Apache Kafka documentation, "Topic-Level Configs" and "Basic Kafka Operations". https://kafka.apache.org/documentation/#topicconfigs ↩︎ ↩︎

  10. Jun Rao, "How to Choose the Number of Topics/Partitions in a Kafka Cluster?", Confluent Blog, March 2015. https://www.confluent.io/blog/how-choose-number-topics-partitions-kafka-cluster/ ↩︎ ↩︎

  11. KIP-500: Replace ZooKeeper with a Self-Managed Metadata Quorum. https://cwiki.apache.org/confluence/display/KAFKA/KIP-500%3A+Replace+ZooKeeper+with+a+Self-Managed+Metadata+Quorum ↩︎

  12. Apache Software Foundation, "What's New in Apache Kafka 3.3", October 3, 2022 ("The 3.3 release now marks KRaft mode as production ready for new clusters only", KIP-833). https://blogsarchive.apache.org/kafka/entry/what-rsquo-s-new-in ↩︎

  13. David Jacot, "Apache Kafka 4.0.0 Release Announcement", March 18, 2025 (first major release without ZooKeeper; KIP-848 GA). https://kafka.apache.org/blog/2025/03/18/apache-kafka-4.0.0-release-announcement/ ↩︎ ↩︎ ↩︎

  14. KIP-405: Kafka Tiered Storage. https://cwiki.apache.org/confluence/display/KAFKA/KIP-405%3A+Kafka+Tiered+Storage ↩︎

  15. Apache Kafka, "Tiered Storage Operations". https://kafka.apache.org/39/operations/tiered-storage/ ↩︎

  16. RabbitMQ, "Exchanges". https://www.rabbitmq.com/docs/exchanges ↩︎

  17. RabbitMQ, "Quorum Queues". https://www.rabbitmq.com/docs/quorum-queues ↩︎

  18. Quix, "Redpanda vs Kafka". https://quix.io/blog/redpanda-vs-kafka-comparison ↩︎

  19. AWS, "Amazon SQS high throughput FIFO queues". https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/high-throughput-fifo.html ↩︎

  20. AWS, "Amazon Kinesis Data Streams: working with streams and shards". https://docs.aws.amazon.com/streams/latest/dev/working-with-streams.html ↩︎

  21. AWS, "Amazon SQS visibility timeout". https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html ↩︎

  22. Christo Lolov, "Apache Kafka 4.2.0 Release Announcement", February 17, 2026 ("Kafka Queues (Share Groups) is now production-ready"). https://kafka.apache.org/blog/2026/02/17/apache-kafka-4.2.0-release-announcement/ ↩︎

  23. Apache Kafka, CooperativeStickyAssignor.java source. https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/clients/consumer/CooperativeStickyAssignor.java ↩︎ ↩︎

  24. KIP-429: Kafka Consumer Incremental Rebalance Protocol. https://cwiki.apache.org/confluence/display/KAFKA/KIP-429%3A+Kafka+Consumer+Incremental+Rebalance+Protocol ↩︎

  25. Apache Kafka documentation, "Consumer Rebalance Protocol". https://kafka.apache.org/42/operations/consumer-rebalance-protocol/ ↩︎

  26. AWS, "Amazon SQS queue quotas" ("a maximum of approximately 120,000 in flight messages", "depending on queue traffic and message backlog"). https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/quotas-queues.html ↩︎

  27. Gregor Hohpe, Bobby Woolf, "Dead Letter Channel" pattern, Enterprise Integration Patterns. https://www.enterpriseintegrationpatterns.com/patterns/messaging/DeadLetterChannel.html ↩︎ ↩︎

  28. LinkedIn, Burrow: Kafka Consumer Lag Checking. https://github.com/linkedin/Burrow ↩︎ ↩︎

  29. Vaibhav Maheshwari, "Load-balanced Brooklin Mirror Maker: Replicating large-scale Kafka clusters at LinkedIn", LinkedIn Engineering, April 11, 2022 ("mirrors more than seven trillion messages per day"; "LinkedIn migrated from Kafka Mirror Maker (KMM) to BMM in 2018"). https://www.linkedin.com/blog/engineering/data-streaming-processing/load-balanced-brooklin-mirror-maker-replicating-large-scale-kaf ↩︎

  30. Codelit, "Handling Failed Messages at Scale: Dead Letter Queue patterns". https://codelit.io/blog/dead-letter-queue-patterns ↩︎