Kafka vs RabbitMQ vs SQS: Log or Queue, Not Speed

The question that actually decides your message queue is not how many messages per second you need. It is whether a message should still exist after somebody reads it. Answer that and two of the three options disappear.

Tech Talk News Editorial10 min read
ShareXLinkedInRedditEmail
Kafka vs RabbitMQ vs SQS: Log or Queue, Not Speed

Key takeaways

  • The choice between Apache Kafka, RabbitMQ and Amazon SQS is a data-structure decision, not a throughput decision: a log retains messages after they are read, and a queue removes a message as soon as a consumer acknowledges it.
  • Kafka's default topic retention is 604,800,000 milliseconds (7 days), and its own documentation describes that setting as an SLA on how soon consumers must read their data, because a consumer tracks its own offset and can rewind to re-read history.
  • Kafka guarantees ordering only within a single partition, RabbitMQ acknowledges and removes messages one at a time through exchanges and routing keys, and Amazon SQS offers standard queues (at-least-once, best-effort ordering) or FIFO queues (strict order per message group, deduplicated inside a 5-minute interval).
  • Amazon SQS FIFO queues are limited to 300 transactions per second per API action without high throughput mode, or 3,000 messages per second using 10-message batches; high throughput mode reaches 70,000 TPS in US East (N. Virginia), US West (Oregon) and Europe (Ireland), but only 2,400 TPS in most other AWS Regions.
  • A three-broker Amazon MSK cluster on kafka.m5.large costs $459.90 a month at $0.21 per broker hour before storage or staff time, while 10 million messages through SQS standard cost about $11.60, and SQS does not reach that cluster price until roughly 383 million messages a month.

Somebody on your team is about to say “we should just use Kafka.” They will say it in a design review, they will point at a throughput number, and everyone will nod, because Kafka is what serious companies use. Six months later you own a cluster, a partition strategy, and a rebalancing bug, and your peak traffic is forty messages a second. I have sat in that meeting more times than I want to admit.

The throughput argument is almost never the real argument. All three of the usual candidates will move more messages than your product will ever generate. What actually separates them is a much smaller question, and it is a question about data structures: after a consumer reads a message, should the message still be there?

A log says yes. The message stays, each consumer tracks its own position, and you can rewind. A queue says no. The message is removed the moment a consumer acknowledges it, and it is gone. Everything else, ordering, routing, retries, pricing, falls out of that one answer.

The whole decision, in one picture

What happens to a message after a consumer reads it

A producer writes one message

  • Order 8812 was paidOne event, written once

The broker stores it

Kafka appends it to a partition. RabbitMQ puts it in a queue. SQS holds it for up to 14 days.

In a log, the message survives being read

  • Billing reads it at offset 4,001Commits its own offset
  • Analytics reads the same messageSeparate consumer group, separate offset
  • A new service reads it next MarchStarts from offset 0 and replays history
Gone the instant it is acknowledgedReading the same message a second timeIn a queue, an acknowledged message is removed from the broker. There is no history to re-read, so a consumer added later sees only future traffic.

Behavior described in the Apache Kafka introduction and the RabbitMQ AMQP 0-9-1 concepts guide.

Takeaway

Ask one question before you compare a single benchmark. Does anything downstream need to read the same message twice? If the honest answer is no, you do not need a log, and you should not pay for one.

A log keeps the message. A queue forgets it.

Kafka's own introduction is blunt about this. Events “can be read as often as needed” and, unlike traditional messaging systems, “are not deleted after consumption.” Instead you set a per-topic retention window, after which old events are discarded.[1] The default is retention.msat 604,800,000 milliseconds, which is seven days, and the config reference describes that setting as “an SLA on how soon consumers must read their data.”[3]

That works because Kafka does not track what has been consumed on the broker. A partition is consumed by exactly one consumer inside a group at a time, so the consumer's position is “just a single integer, the offset of the next message to consume.”[2] Acknowledgment becomes a number you checkpoint instead of state the broker has to hold per message. And then the design doc drops the line I think about constantly: a consumer can rewind to an old offset and re-consume data, which “violates the common contract of a queue, but turns out to be an essential feature for many consumers.”[2]

The first time I read that I laughed, because it is the whole argument stated by the people who built the thing. Kafka is not a better queue. It is deliberately not a queue.

Now the other side. In the AMQP 0-9-1 model that RabbitMQ implements, “a broker will only completely remove a message from a queue when it receives a notification for that message.”[5] The acknowledgment is the delete. RabbitMQ's own reliability guide puts it in the same terms: a consumer ack confirms the delivery was processed “so the delivered message can be marked for future deletion.”[6] SQS works the same way from the caller's side. You receive, you work, you call DeleteMessage, and the message stops existing.

Kafka is not a better queue. Its own design documentation says that rewinding an offset violates the common contract of a queue. That is the product, and it is also the bill.

The three systems on one axis

Here is where each one actually sits, with the numbers that matter rather than the ones on the landing page.

Apache Kafkais a partitioned append-only log. A topic is spread across partitions on different brokers, events with the same key land in the same partition, and Kafka guarantees that any consumer of a given topic-partition reads that partition's events “in exactly the same order as they were written.”[1] Read that guarantee carefully, because it is narrower than people remember. There is no global ordering across a topic. If you need two events to be processed in order, they have to share a key, and if too many events share a key you have built a hot partition.

RabbitMQ is a broker that routes. Publishers do not write to queues, they write to exchanges, and exchanges distribute copies to queues using bindings. A direct exchange matches the routing key exactly, a fanout exchange copies to every bound queue and ignores the key, and a topic exchange matches the key against a pattern.[5] That routing layer is the thing Kafka does not have, and it is genuinely useful when one event needs to reach three different consumers with three different filters.

Amazon SQSis a queue you do not run. Standard queues support “a nearly unlimited number of API calls per second” and give you at-least-once delivery, with the honest caveat printed right in the docs: “more than one copy of a message might be delivered, and messages may occasionally arrive out of order.”[9] FIFO queues give you strict ordering, but only within a message group ID, and messages in different groups may be processed out of order relative to each other.[10]

QuestionApache KafkaRabbitMQAmazon SQS
Underlying structurePartitioned append-only logQueues fed by exchanges and bindings, plus StreamsManaged queue, standard or FIFO
After a consumer readsMessage stays until retention expires (default 7 days)Removed when the consumer acknowledgesRemoved when the consumer calls DeleteMessage
Ordering guaranteeStrict within a partition, none across a topicPer queue, weakened by requeues and parallel consumersStandard: best effort. FIFO: strict per message group
Replay historyYes, reset the consumer offsetOnly on a Stream, not on a queueNo
Failure handlingYour own retry logic around the offset commitDead letter exchange, delivery limit 20 on quorum queuesVisibility timeout plus maxReceiveCount to a DLQ
What you operateA broker cluster, storage, partitions, upgradesA node or cluster, or a managed brokerNothing

Takeaway

Only one row in this table is hard to change later, and it is the third one. Retention and replay are architectural. Routing, retries and hosting are configuration.

Side note

The log-versus-queue split stopped being a vendor fight a while ago and most comparison posts have not noticed. RabbitMQ ships Streams, which model “an append-only log of messages that can be repeatedly read until they expire,” described in the docs as non-destructive consumer semantics, with consumers attaching at any offset in the log.[7] So you can have the log without the Kafka cluster. That does not make Streams a Kafka replacement at scale, but it kills the argument that wanting replay forces you into a Kafka migration.

Consumer groups, and the rebalance nobody budgets for

Kafka's parallelism model is the part that bites teams after launch. All consumers sharing a group.id form one consumer group, and partitions are divided among them. If a process fails, its partitions are reassigned to other consumers in the group. If a new consumer joins, partitions are moved to it. Adding partitions to a subscribed topic triggers the same thing.[4]

That reassignment is a rebalance, and because each partition is consumed by exactly one consumer in a group at a time, your partition count is a hard ceiling on how many workers can share the load.[2] Twelve partitions means twelve useful consumers. The thirteenth pod sits there doing nothing while your dashboard shows thirteen healthy replicas. I have watched a team scale that deployment to twenty and wonder why the lag graph did not move.

Here is the current detail worth knowing. Kafka 4.0 made the next-generation rebalance protocol from KIP-848 generally available, and it is fully incremental, with no global synchronization barrier, which is exactly the stop-the-world pause older clusters are famous for.[8] It is enabled on the server automatically. It is not enabled on the consumer by default, you have to set group.protocol to consumer.[8] The fix shipped. Most clusters are still running without it because nobody read the release notes.

SQS has none of this, which is the actual selling point. There is no group membership, no assignment protocol, and no ceiling on consumer count for standard queues. You add workers and they pull. If you want per-key ordering you use a FIFO queue and a message group ID, and parallelism becomes a function of how many distinct groups you have.[10]

The settings that decide whether this works at 3am

Every one of these systems is easy in the happy path. The differences show up when a consumer dies holding a message, and that is where the defaults matter.

SQS visibility timeout. When you receive a message it becomes invisible to other consumers for a fixed window: 30 seconds by default, 12 hours maximum.[11] If your handler takes longer than the timeout and has not deleted the message, SQS makes it visible again and somebody else picks it up. A 90-second job on a default queue does not fail loudly. It succeeds three times.

Dead-letter queues. SQS moves a message to a DLQ after maxReceiveCount receives, set in the redrive policy.[12] RabbitMQ dead-letters a message when it is rejected without requeue, when its TTL expires, when the queue exceeds a length limit, or when it exceeds the quorum queue delivery limit, which has defaulted to 20 since RabbitMQ 4.0 (before that it was unlimited).[13] Kafka has no built-in dead-letter concept at all. You write that yourself.

Heads up

One SQS detail that has ruined a debugging session for me. For standard queues, a message's expiry is always based on its original enqueue timestamp, and moving it to a dead-letter queue does not reset that clock.[12] A message that sat one day in the source queue before failing gets three days in a DLQ with a four-day retention, not four. Set your DLQ retention longer than the source queue's, or your evidence deletes itself while you are asleep.

Requeue order. RabbitMQ tries to put a requeued message back in its original position, and if it cannot, because other consumers acknowledged concurrently, the message goes closer to the head of the queue.[6] So a queue you believed was ordered quietly is not, the first time a consumer nacks under load. That is worth pairing with a circuit breaker on the failing dependency, because a requeue loop and a dying downstream service make each other worse.

Duplicates. SQS FIFO deduplicates on a message deduplication ID, and the window is exactly 5 minutes.[14] AWS calls this exactly-once processing, and inside that window it is. Outside it, or when your worker crashes after doing the work but before deleting the message, the message comes back. This is the same at-least-once world every broker lives in, covered in more depth in the delivery guarantees explainer, and the fix is not broker configuration. It is an idempotency key on your handler.

What you run versus what you rent

Now the part that decides more architectures than anyone admits in the design doc. Take a real service moving 10 million messages a month, which is roughly four a second. Each message costs three SQS API calls: send, receive, delete. That is 30 million requests. The first million every month are free, and standard requests are $0.40 per million in US East (N. Virginia), so the bill is $11.60.[15]

The comparable managed Kafka is Amazon MSK. A minimum production cluster is three brokers, and kafka.m5.largeis $0.21 per broker hour in the same Region. Three brokers times 730 hours is $459.90 a month, before the $0.10 per GB-month of storage, and before anybody's salary.[16]

$11.60
10M messages/month on SQS standard, us-east-1
$459.90
3-broker MSK cluster, kafka.m5.large, before storage
~383M
Messages/month before SQS costs as much as that cluster
~148/sec
Sustained rate that crossover implies, every second, all month

Takeaway

SQS is cheaper than a minimum Kafka cluster until you are sustaining roughly 148 messages a second, and batching up to 10 messages per API call pushes that crossover further out. Most products never get there. The ones that do usually know it years in advance.

And the dollar figure is the small half of the cost. The cluster is the other half: partition counts you have to guess before you have traffic, broker upgrades, disk forecasting against a seven-day retention window, and one engineer who becomes The Kafka Person and cannot take a vacation during peak season. None of that appears in the pricing page comparison people paste into the design doc.

This is the same trap as reaching for service boundaries too early, which I got into in the monolith versus microservices piece. The technology is not wrong. The timing is.

How I would actually pick

Three questions, in order. Stop at the first one that gives you a clear answer.

  1. Does anything need to read the same message twice? A new consumer backfilling from history, a reprocess after a bug, an analytics pipeline reading the same events as your billing service. If yes, you want a log, and that means Kafka or a RabbitMQ Stream. If no, stop here, you want a queue, and you just eliminated the option with an operations bill.
  2. Does one message need to reach several different consumers with different rules?That is routing, and RabbitMQ's exchanges do it natively with direct, fanout and topic matching.[5] Doing the same thing on SQS means Amazon SNS in front of several queues, which is fine but is a second service.
  3. Does order actually matter, and at what granularity? Order per customer, per account, per document is a message group ID on SQS FIFO or a partition key on Kafka. Global order across every message is a single message group or a single partition, which caps you at one consumer and you should be very sure you need it. Remember the FIFO ceilings: 300 TPS per API action, or 3,000 messages per second with 10-message batches, and high throughput mode is 70,000 TPS in three Regions, 18,000 TPS in US East (Ohio) and Europe (Frankfurt), and 2,400 in most of the rest.[11]

My honest default for a product that has not proven it needs anything else: SQS standard, a dead-letter queue with maxReceiveCount set to something like 5, a visibility timeout longer than your slowest handler, and idempotent consumers. It costs eleven dollars and nobody has to know how partitions work. Move to a log when a real replay requirement shows up, not when somebody wants Kafka on their resume.

The pattern I keep seeing is teams buying Kafka for throughput they will never reach, then paying the operational bill for a replay feature they never use. If that describes your cluster, the honest move is not a benchmark. It is asking your team when anyone last reset an offset, and sitting with the answer.

Sources and further reading

  1. 1.PrimaryApache Kafka, "Introduction". Topics as durable event storage, events not deleted after consumption, per-topic retention, partitions and the per-partition ordering guarantee.
  2. 2.PrimaryApache Kafka 4.3 documentation, "Design". Consumer position as a single integer offset, one consumer per partition per group, and the note that rewinding an offset violates the common contract of a queue.
  3. 3.PrimaryApache Kafka 4.3 documentation, "Topic Configs". retention.ms default of 604800000 (7 days), described as an SLA on how soon consumers must read their data.
  4. 4.PrimaryApache Kafka, "KafkaConsumer" API documentation. Consumer groups keyed on group.id, and the events that trigger a group rebalance: process failure, a new consumer joining, and new partitions on a subscribed topic.
  5. 5.PrimaryRabbitMQ, "AMQP 0-9-1 Model Explained". Exchanges, bindings and routing keys; direct, fanout, topic and headers exchange types; and the rule that a broker removes a message from a queue only when it receives an acknowledgment.
  6. 6.PrimaryRabbitMQ, "Consumer Acknowledgments and Publisher Confirms". Acknowledgment marks a delivered message for future deletion; requeued messages return to their original position where possible, otherwise closer to the queue head.
  7. 7.PrimaryRabbitMQ, "Streams and Superstreams". Streams as an append-only log with non-destructive consumer semantics, repeatedly readable, with consumers attaching at a chosen x-stream-offset.
  8. 8.PrimaryApache Kafka 4.3 documentation, "Consumer Rebalance Protocol". KIP-848 generally available since Kafka 4.0, fully incremental with no global synchronization barrier, enabled on the server automatically but requiring group.protocol=consumer on the client.
  9. 9.PrimaryAmazon SQS Developer Guide, "Amazon SQS standard queues". Nearly unlimited API calls per second, at-least-once delivery, possible duplicate copies and occasional out-of-order arrival with best-effort ordering.
  10. 10.PrimaryAmazon SQS Developer Guide, "FIFO queue delivery logic". Strict ordering within a message group ID, no ordering guarantee across groups, and parallel processing across groups.
  11. 11.PrimaryAmazon SQS Developer Guide, "Amazon SQS message quotas". Visibility timeout default 30 seconds and maximum 12 hours; retention default 4 days and maximum 14 days; FIFO limits of 300 TPS per API action, 3,000 messages/second batched, and high throughput figures of 70,000 TPS in three Regions down to 2,400 TPS elsewhere.
  12. 12.PrimaryAmazon SQS Developer Guide, "Using dead-letter queues in Amazon SQS". The redrive policy and maxReceiveCount, plus the retention rule that a standard queue message keeps its original enqueue timestamp when moved to a dead-letter queue.
  13. 13.PrimaryRabbitMQ, "Quorum Queues". Quorum queues as the default replicated queue type, and the delivery limit defaulting to 20 starting with RabbitMQ 4.0 after being unlimited in the 3.13.x series.
  14. 14.PrimaryAmazon SQS Developer Guide, "FIFO queue and message identifiers". Message deduplication ID and the 5-minute deduplication interval, within which duplicate sends deliver only one copy.
  15. 15.DataAWS Price List API, Amazon SQS, US East (N. Virginia). First 1,000,000 requests per month free; $0.40 per million standard requests and $0.50 per million FIFO requests in the first pricing tier. Retrieved August 2026.
  16. 16.DataAWS Price List API, Amazon MSK, US East (N. Virginia). $0.21 per broker hour for kafka.m5.large and $0.10 per GB-month of broker storage. Retrieved August 2026.

Frequently asked questions

What is the difference between a log and a queue in messaging?
A log keeps each message after it is read, and a queue deletes it. In a log like Kafka, every consumer tracks its own position (an offset), so a second consumer can be added later and read the whole history from the beginning. In a queue like RabbitMQ or Amazon SQS, the broker removes the message once a consumer acknowledges it, so there is nothing left to replay and adding a consumer later gets you only future traffic.
Should I use Kafka or RabbitMQ?
Use Kafka if something downstream genuinely needs to read the same message twice, and RabbitMQ if each message has exactly one job to do. Kafka is a partitioned append-only log with configurable retention, which buys you replay, multiple independent consumer groups, and ordering within a partition. RabbitMQ is a broker with exchanges, bindings and routing keys, which buys you per-message acknowledgment, flexible routing, and dead-letter exchanges. RabbitMQ also ships Streams, an append-only log with non-destructive consumer semantics, so wanting replay does not by itself force you onto Kafka.
What is the throughput limit of an SQS FIFO queue?
An SQS FIFO queue is limited to 300 transactions per second per API action without high throughput mode, which becomes 3,000 messages per second if you batch 10 messages per call. With high throughput mode enabled, the non-batched limit is up to 70,000 TPS in US East (N. Virginia), US West (Oregon) and Europe (Ireland), 18,000 TPS in US East (Ohio) and Europe (Frankfurt), and 2,400 TPS in all other AWS Regions.
Does SQS FIFO really give you exactly-once processing?
It gives you exactly-once delivery inside a 5-minute deduplication interval, which is not the same thing as exactly-once processing of your business logic. If two sends carry the same message deduplication ID within 5 minutes, only one copy is delivered. Outside that window, or if your consumer crashes after doing the work but before deleting the message, the message comes back. Handlers that charge cards or send email still need their own idempotency key.
What does a visibility timeout do in Amazon SQS?
It hides a received message from other consumers for a fixed period so two workers do not process it at once, and it defaults to 30 seconds with a maximum of 12 hours. If the consumer deletes the message before the timeout expires, the message is gone. If it does not, the message becomes visible again and is redelivered, which is why a job that takes 90 seconds under a 30-second timeout will silently run over and over.

Written by

Tech Talk News Editorial

Computer engineering background. Writes about software, AI, markets, and real estate, and the places where the three meet.

More about the author
ShareXLinkedInRedditEmail