RabbitMQ vs Kafka vs Redis Streams: Choosing a Message Queue
"Message queue" is not one thing. RabbitMQ (traditional AMQP and flexible routing), Kafka (a distributed log for high-throughput stream processing), and Redis Streams (lightweight streams) have fundamentally different designs. This article explains their core models, strengths, and how to choose by requirements rather than popularity.
Key Points
- Topic Focus: DevOps
- Suitable for: indie hackers, full-stack engineers, AI application developers
- Core value: quickly understand background, practical paths, common pitfalls, and engineering advice
RabbitMQ vs Kafka vs Redis Streams: Choosing a Message Queue
Whenever "add a message queue" comes up in an architecture review, an argument almost inevitably follows: Kafka or RabbitMQ? In recent years, another option has joined the discussion: Redis Streams. By the end, it often becomes clear that the people arguing have not distinguished the fact that these three systems are fundamentally different things. They simply happen to share the label "message queue."
The purpose of this article is not to take sides. All three are outstanding tools in their respective domains. The key question is: is your problem actually the kind of problem each one excels at solving? We will first explain their core models, then discuss how to choose based on your requirements.
1. First Understand This: Message Queues Are Not All the Same Thing
Many people conflate these concepts, but if you read only one section, read this one:
| System | Essence | Message lifecycle |
|---|---|---|
| RabbitMQ | Queues + exchanges (intelligent routing) | Deleted after consumption is acknowledged |
| Kafka | Distributed commit log (append-only log) | Retained according to policy and can be consumed repeatedly |
| Redis Streams | Lightweight in-memory log stream | Retained according to policy, but constrained by memory |
These three statements determine every difference that follows:
- RabbitMQ is a message dispatcher. A message enters the system, is routed to a queue, is taken and acknowledged by a consumer, and then disappears. Its core purpose is to "reliably deliver this message to its intended recipient."
- Kafka is a log. Producers append records to the end of the log, while each consumer maintains an offset indicating how far it has read. Consumers can replay records, rewind, or read independently of one another. A message does not disappear simply because it has been read.
- Redis Streams is a lightweight stream between the other two. Its data model resembles Kafka, with an append-only log and consumer groups, but it runs in Redis memory and does not have Kafka's heavyweight ecosystem.
Once you understand this distinction, none of the design choices that follow, including routing, ACKs, partitions, and retention policies, are arbitrary. They all arise from these three underlying models.
2. RabbitMQ: Classic AMQP and the Art of Routing
2.1 Core Model: Exchange + Queue
The essence of RabbitMQ is decoupling routing logic. Producers do not place messages directly into queues. They publish them to an Exchange, which sends each message to one or more Queues according to a set of rules. This rule-based step is RabbitMQ's strongest capability.
Remember these four Exchange types and you will have what you need:
- direct: Performs an exact match on
routing_key. For example, withrouting_key = "error", only queues bound toerrorreceive the message. - topic: Performs pattern matching and supports wildcards in
routing_key.logs.*.errormatches bothlogs.app.errorandlogs.db.error. This is the most commonly used type. - fanout: Broadcasts the message unchanged to every bound queue and ignores
routing_key. It is suitable when "multiple systems must process the same event." - header: Matches message headers instead of relying on
routing_key. It is uncommon in real projects, but can occasionally be invaluable for complex routing conditions.
Consider a typical scenario: an order system publishes an order.created event that must be consumed by inventory, loyalty, notification, and risk-control services. In RabbitMQ, you create one topic exchange, give each service its own queue, and bind each queue to a key such as order.# or order.created as needed. They do not interfere with one another, and each acknowledges and retries messages independently.
2.2 ACKs, Retries, and Dead-Letter Queues: The Moat Around Reliable Delivery
RabbitMQ takes the requirement that "every message must be processed correctly" very seriously:
- Manual ACK: A consumer sends
basic_ackonly after it has finished processing a message. If the process crashes during handling, no ACK is sent, so the message is redelivered to another consumer. - prefetch: Limits the number of unacknowledged messages assigned to a single consumer, preventing one consumer from being overwhelmed while others remain idle.
- Dead-letter queue (DLX): Messages can be routed to a designated dead-letter exchange when they receive
basic_reject/basic_nackwithout requeueing, when their TTL expires, or when the queue is full. Failed messages are handled centrally instead of being lost. - Delayed queue: Traditionally implemented with TTL + DLX. RabbitMQ now also has the official
rabbitmq_delayed_message_exchangeplugin, which provides a more direct approach.
These mechanisms make RabbitMQ almost unrivaled for scenarios such as task distribution, order processing, and payment callbacks, where every message must be preserved and produce a result.
2.3 Where RabbitMQ Fits
- Task queues: Background jobs such as Worker pools, Celery, and Sidekiq
- Event notifications: Fanout of business events to multiple subscribers
- RPC: Asynchronous RPC using a reply-to queue + correlation_id
- Complex routing: Distributing multiple message types to different consumers according to rules
- Traditional microservice communication: Reliable delivery, ACKs, and retries
Poor fit: Messages that must be consumed repeatedly through replay, extremely high-throughput log streams at the million-TPS level, or long-term event sourcing.
2.4 Deployment and Operations
- Written in Erlang, with distribution based on clustering + mirrored queues. Quorum Queue is now recommended. It is based on Raft and is more reliable than the old mirror queue.
- A single node can handle tens of thousands to more than one hundred thousand TPS, with further reductions across clusters.
- Operational complexity is moderate, and both the CLI (
rabbitmqctl) and management UI are mature.
3. Kafka: A Distributed Commit Log Built for High Throughput
3.1 Core Model: Topic → Partition → Offset
Kafka's core abstraction is not a queue. It is a partitioned log:
- A Topic is divided into N Partitions.
- Each Partition is an ordered, immutable, continuously appended log.
- Messages are ordered within a Partition but not across Partitions.
- Every message has a monotonically increasing offset.
- Consumers maintain their own offsets and can read from any position. If they want to rewind and reread data, they can simply do so.
The key consequence of this model is that consumption is completely decoupled from production-side "deletion." Producers only append to the end of the log. Consumers maintain their own offsets without affecting one another. The same message can be consumed once by each of N different consumer groups, with none of them needing to know that the others exist.
3.2 Consumer Groups: Scaling and Parallelism
The Consumer Group is central to Kafka:
- Consumers in the same Group divide all Partitions among themselves. A Partition is consumed by only one consumer within the Group.
- Need more consumption parallelism? Add Partitions and consumers. However, when the number of consumers exceeds the number of Partitions, the extra consumers do no work.
- Different Groups do not affect one another. Each independently consumes the complete dataset.
This mechanism makes Kafka a natural fit for "one event stream consumed independently by N downstream systems," which is a core requirement of event-driven architectures and data platforms.
3.3 Message Retention: Kafka's Real Killer Feature
RabbitMQ deletes messages after consumption, while Kafka retains them for seven days by default, with configurable retention, regardless of whether consumers have read them.
This distinction looks simple, but it completely changes what you can do:
- Event replay: Launch a new consumer, start from an offset one week in the past, and feed the entire historical dataset through it for recomputation. RabbitMQ cannot do this.
- Failure recovery: If a consumer recovers after being down for three hours, it resumes from its last committed offset without losing data.
- Event Sourcing: Store every state change as an event so the current state can be reconstructed at any time.
- Stream processing: Use Kafka + Streams / Flink / Spark Streaming to treat messages as streams and compute windowed aggregations and Joins.
- Data pipelines (ETL): Use Kafka Connect to send database binlogs, logs, and metrics into Kafka, where downstream systems can consume them as needed.
3.4 Performance and Reliability
- Sequential disk writes + zero-copy (sendfile) allow a single node to handle hundreds of thousands to more than one million TPS with ease.
- Replicas + ISR (In-Sync Replicas) provide durability.
- An idempotent producer + transactions can provide exactly-once semantics, at a performance cost.
- Ordering is guaranteed only within a Partition. Global ordering requires a single Partition, which limits throughput. This is an inherent Kafka trade-off.
3.5 Where Kafka Fits
- Log aggregation: Application logs, access logs, and audit logs
- Event streams / stream processing: Real-time data warehouses, user behavior analytics, and risk control
- Data pipelines (ETL): CDC and data synchronization
- Event Sourcing / CQRS: Workloads that require replay or state reconstruction
- High-throughput message bus: The event backbone between microservices
Poor fit: Complex routing rules, since Kafka has nothing like an exchange and relies on Topic naming conventions; task queues that require strict reliable delivery, retries, and dead-letter handling for every message, which are cumbersome to implement; and small projects where the operational cost is not justified.
3.6 Deployment and Operations
- Heavyweight. Zookeeper / KRaft, Brokers, replication, monitoring with Burrow and Kafka Exporter, Schema Registry, Connect, and the rest of the stack can occupy an entire team.
- KRaft mode, which removes Zookeeper, has simplified deployment considerably, but Kafka remains enterprise-grade infrastructure.
- Managed Kafka services in the cloud, such as MSK, Confluent Cloud, and Upstash, can eliminate much of the operational burden.
4. Redis Streams: A Lightweight Stream Hidden Inside Redis
4.1 What It Is
The Streams data structure introduced in Redis 5.0 is essentially an in-memory append-only log + consumer group model. Its API closely resembles Kafka:
XADDwrites a message and automatically generates an ID in<timestamp>-<seq>format.XREAD/XREADGROUPconsume messages.XGROUP CREATEcreates a consumer group.XPENDING/XCLAIMhandle orphaned messages by allowing unacknowledged messages from a failed consumer to be claimed by another.XACKacknowledges a message.XLEN,XRANGE, andXREVRANGEsupport random reads and replay.MAXLEN/MINIDlimit stream length and perform Trim operations.
See the pattern? This is a miniature Kafka, with the same log + consumer group model, except that it runs in Redis memory.
4.2 Key Differences from Kafka
| Dimension | Kafka | Redis Streams |
|---|---|---|
| Storage | Disk log | Redis memory, with optional RDB/AOF persistence |
| Per-message size | Well suited to KB-scale messages | Constrained by memory; use large messages cautiously |
| Throughput | Millions of TPS | Hundreds of thousands of TPS per node, limited by memory bandwidth |
| Retention | Long-term retention by time/size by default | Constrained by memory and must be Trimmed |
| Cluster scaling | Native partitioning + replicas | Redis Cluster sharding (Streams cannot span slots) |
| Operational cost | High | Very low if Redis is already deployed |
4.3 Key Differences from RabbitMQ
Redis Streams has no exchange / routing. If you need to distribute messages to different queues according to rules, RabbitMQ is the better choice. Routing in Redis Streams can essentially be simulated only by manually using different stream names.
4.4 Where Redis Streams Fits
- Event streams for small and medium-sized projects: User presence, IM messages, and real-time counters
- Cross-process Pub/Sub + message retention: Native Redis Pub/Sub loses messages when a subscriber disconnects; Streams solves this problem
- Lightweight task queues: If you do not want to deploy RabbitMQ, Streams + consumer groups can handle the job
- Redis-heavy workloads such as cache warming and real-time leaderboards: Reuse the existing Redis deployment and reduce operational overhead
Poor fit: Large datasets or event streams requiring long retention, because they may not fit in memory; workloads with extremely strict durability requirements where absolutely no data can be lost, because Redis uses asynchronous persistence by default and a crash can lose the most recent few seconds; and complex routing rules.
4.5 An Often-Overlooked Point: Streams ≠ Redis Pub/Sub
The original Redis Pub/Sub model delivers messages only while subscribers are connected. Disconnected subscribers miss messages, and the messages are not retained. Streams was introduced largely to address this limitation. If your project still uses Pub/Sub to distribute critical events, it is time to switch to Streams.
5. Side-by-Side Comparison: The Differences in One Table
| Dimension | RabbitMQ | Kafka | Redis Streams |
|---|---|---|---|
| Core model | Queues + exchanges | Distributed commit log | In-memory log stream |
| After consumption | Deleted after ACK | Retained and replayable | Retained and replayable, but requires Trim |
| Routing capability | Extremely strong, with 4 exchange types | Weak, based on Topic naming | None, based on Stream naming |
| Ordering guarantee | Ordered within a single queue | Ordered within a single Partition | Ordered within a single Stream |
| Throughput | Tens of thousands to more than one hundred thousand TPS | Millions of TPS | Hundreds of thousands of TPS |
| Latency | Low, measured in milliseconds | Moderate, depending on batch size | Extremely low |
| Message retention | No retention | Long, seven days by default and optionally permanent | Short, constrained by memory |
| Consumer scaling | Add queues and consumers | Add Partitions and consumers | Add consumers; a single Stream has no Partitions |
| Reliable delivery | Strong, with ACK + DLX | Strong, with replicas + ISR | Moderate, depending on persistence configuration |
| Replay capability | None | Strong | Strong |
| Operational complexity | Moderate | High | Low, and almost zero if Redis is already deployed |
| Learning curve | Moderate | Relatively steep | Gentle |
| Typical scenarios | Task queues, RPC, and event notifications | Logs, stream processing, ETL, and event sourcing | Lightweight event streams and projects that already use Redis |
6. Selection Decisions: Choose by Requirements, Not Popularity
Everything above can be condensed into a decision tree:
1. Do you need "task distribution," "reliable delivery," or "complex routing"?
Examples include order processing, email delivery, payment callbacks, Worker pools, cross-service RPC, and routing one event to different consumers according to rules.
→ Choose RabbitMQ. Its exchanges, ACKs, DLX, and prefetch are built for these scenarios. Kafka can also do this, but it is awkward because you must implement retries, dead-letter handling, and delayed queues yourself.
2. Do you need a "high-throughput event stream," "logs," or "replay"?
Examples include user behavior logs, monitoring metrics, risk-control event streams, real-time data warehouses, CDC data pipelines, event sourcing, and multiple downstream systems independently consuming complete copies of the data.
→ Choose Kafka. This is its irreplaceable domain. Replay, multiple consumer groups, million-level TPS, and long-term retention are capabilities that RabbitMQ / Redis Streams cannot provide at the same level.
3. Do you need a "lightweight event stream," already use Redis, or want to avoid introducing new middleware?
Examples include IM messages, user presence, real-time notifications for small and medium-sized applications, and Web applications that rely heavily on Redis for caching.
→ Choose Redis Streams. One data structure handles the message stream, with almost no additional operational cost.
4. Still unsure? Then answer one question first: do you need to replay messages?
- Need replay → Choose between Kafka and Redis Streams based on scale and existing infrastructure
- No replay needed, and every message should disappear after processing → RabbitMQ
7. Do Not Adopt Kafka Just to "Use Kafka"
This deserves its own section because I have seen too many teams fall into this trap.
Kafka is so prominent in the technology industry that it often becomes a form of "political correctness." As soon as teams adopt microservices or an event-driven architecture, their first reaction is "use Kafka." But Kafka's costs are very real:
- Operational cost: Operating a Kafka cluster, including Zookeeper / KRaft, monitoring, Connect, and Schema Registry, carries a burden comparable to adding half a middleware team.
- Complexity: How many Partitions should you create? How many replicas? How long should data be retained? How should consumer rebalances be handled? How should offsets be committed? You must understand all of these questions, or production incidents are inevitable.
- Poor value at low throughput: If your workload is only a few thousand TPS, one RabbitMQ node is enough. A Kafka cluster is overengineering.
I once saw a team force order status changes, a classic task-distribution workload where every message required reliable delivery, retries, and dead-letter handling, into Kafka. To implement "retry after consumption failure + dead-letter handling," they went through an elaborate retry topic + dead letter topic design. The code was extremely verbose and bugs were constant. With RabbitMQ, the same work would have taken half a day.
I have also seen the reverse: a log aggregation workload handling terabytes of data per day ran on RabbitMQ for six months. The cluster was unstable every day, but after the team migrated to Kafka, everything ran smoothly.
Both failures came from the same mistake: choosing a tool for its "reputation" instead of choosing it for the actual "problem."
So here is the final point:
When choosing a message queue, first understand the problem you need to solve. Use RabbitMQ for task distribution, Kafka for high-throughput event streams, and Redis Streams for lightweight workloads. Do not let a tool's reputation make architectural decisions for you.
Summary
-
The three systems are fundamentally different: RabbitMQ uses "queues + exchanges" for message distribution and deletes messages after consumption. Kafka uses a "distributed commit log" for retained, replayable event streams. Redis Streams provides a "lightweight in-memory stream," essentially a miniature Kafka. These differences exist at the design-philosophy level, not the configuration level.
-
The central selection question is whether you need replay: If you need replay and multiple consumer groups that each consume the complete stream, choose Kafka or Redis Streams. If you do not need replay but require reliable delivery and complex routing, choose RabbitMQ.
-
RabbitMQ's moat is routing and reliable delivery: Four exchange types, ACKs, dead-letter handling, and prefetch. It remains unmatched for task queues, RPC, and event notifications.
-
Kafka's moat is high throughput + replay: Million-level TPS, time-based retention, multiple consumer groups, and Partition-level parallelism. It is irreplaceable for logs, stream processing, ETL, and event sourcing.
-
Redis Streams is the "good enough and easy to operate" choice: If you already have Redis, the workload is not large, and you need a lightweight event stream, choose it and avoid adding another middleware system.
Key takeaway: choose a message queue based on the shape of your problem, not on which technology looks best on a resume. Kafka is not an upgraded version of RabbitMQ; they are different things. Redis Streams is a third option, not a cheap substitute for RabbitMQ.
Related
Related Content
Article
Considerations for Deploying a Next.js Community Project with Docker
Technical trends, engineering practices, and an independent developer perspective on DevOps and Next.js.
Article
How Can OpenTelemetry Help You Observe AI Applications?
Technical trends, engineering practices, and an independent developer's perspective on DevOps and AI.
Article
Long-Running Tool Calls: The Terminal-State Protocol for Operation, Poll, and Cancel
After the initial request times out, the tool may still be running server-side; issuing a cancel does not mean the side effects have stopped. This article establishes an operation identity, polling watermark, cancellation race handling, and a single terminal-state acceptance point for a single long-running tool call.
Article
Agent Memory Erasure Propagation: From User Forgetting Request to Verifiable Purge
Deleting one row in the primary table won't clear vectors, summaries, caches, or restored backups. This article uses a deletion state machine, generational tombstones, derived lineage, and end-to-end canary verification to truly forget.