Kafka vs RabbitMQ vs Pulsar: A Comparison
Kafka, RabbitMQ and Pulsar compared for data and application teams: the queue model against the log model, shown by sending the same 1,000 events through live RabbitMQ and Kafka brokers, where a second reader got nothing from the queue and everything from the log, then routing, ordering, retention and Pulsar's design.
GloryTecks Team
Training & Placement Faculty
Last updated: 1 October 2026
Kafka, RabbitMQ and Pulsar all move messages from producers to consumers, and all three can be made to do each other's jobs. But each was designed around a different idea, and choosing well means knowing which idea fits your workload. The central distinction is queue versus log.
Queue and log, on live brokers
A queue hands each message to one consumer and removes it once acknowledged. A log keeps every event in order and lets each reader keep its own position. The script sends the same 1,000 events, the first 1,000 trips of a public taxi dataset, to a RabbitMQ queue and a Kafka topic, then reads each with two independent readers, a billing service and an audit service:
import json
import urllib.request
import uuid
from pathlib import Path
import pika
import pyarrow.parquet as pq
from confluent_kafka import Consumer, Producer
from confluent_kafka.admin import AdminClient, NewTopic
name = "yellow_tripdata_2024-01.parquet"
if not Path(name).exists():
urllib.request.urlretrieve(f"https://d37ci6vzurychx.cloudfront.net/trip-data/{name}", name)
rows = pq.read_table(name, columns=["PULocationID", "fare_amount"]).slice(0, 1000).to_pylist()
events = [json.dumps({"zone": r["PULocationID"], "fare": r["fare_amount"]}) for r in rows]
run = uuid.uuid4().hex[:8] # fresh queue and topic names for each run
# RabbitMQ: a queue. A message is delivered to one consumer and removed once acknowledged.
channel = pika.BlockingConnection(pika.ConnectionParameters("localhost")).channel()
queue = f"trips-{run}"
channel.queue_declare(queue=queue, durable=True)
for event in events:
channel.basic_publish(exchange="", routing_key=queue, body=event)
def drain(label):
count = 0
while True:
method, _, body = channel.basic_get(queue=queue)
if method is None:
break
channel.basic_ack(method.delivery_tag) # acknowledged: the broker deletes it
count += 1
print(f"RabbitMQ {label}: {count} messages")
drain("billing service reads")
drain("audit service reads")
# Kafka: a log. Events stay on the topic; each consumer group has its own position.
BROKER, topic = "localhost:9092", f"trips-{run}"
admin = AdminClient({"bootstrap.servers": BROKER})
admin.create_topics([NewTopic(topic, num_partitions=1, replication_factor=1)])[topic].result()
producer = Producer({"bootstrap.servers": BROKER})
for event in events:
producer.produce(topic, value=event)
producer.flush()
def read(group):
consumer = Consumer({"bootstrap.servers": BROKER, "group.id": f"{group}-{run}", "auto.offset.reset": "earliest"})
consumer.subscribe([topic])
count, idle = 0, 0
while idle < 5:
msg = consumer.poll(1.0)
if msg is None:
idle += 1
elif not msg.error():
count, idle = count + 1, 0
consumer.close()
print(f"Kafka {group} group reads: {count} events")
read("billing")
read("audit")RabbitMQ billing service reads: 1000 messages
RabbitMQ audit service reads: 0 messages
Kafka billing group reads: 1000 events
Kafka audit group reads: 1000 eventsThe RabbitMQ queue gave all 1,000 messages to the first reader. Once acknowledged they were gone, and the second reader found the queue empty. The Kafka topic gave all 1,000 events to both groups, because events are retained by policy and not deleted when consumed (Kafka docs: introduction).
Neither behaviour is a defect. A queue is right when a message is a task that must be done once: send this email, resize that image. A log is right when events are facts that several systems need: an order was placed, and billing, analytics and fraud detection each want to know.
RabbitMQ: flexible routing for tasks
RabbitMQ implements a routing model. Publishers send messages to exchanges, and exchanges route copies to queues according to bindings, with direct, fanout, topic and headers exchange types (RabbitMQ docs: AMQP concepts). A queue is a first-in-first-out collection; a message is removed once a consumer acknowledges it, and several consumers can share a queue to spread work (RabbitMQ docs: queues).
So the way to give both billing and audit a copy in RabbitMQ is not to share a queue but to bind two queues to a fanout exchange, one per service. That works well, but each consumer must exist before the message is published; a service added next month cannot read last month's messages. RabbitMQ's strengths are routing flexibility, per-message acknowledgement and redelivery, and simple operation for moderate volumes. Replicated quorum queues provide durability. It also offers streams, an append-only log queue type, for cases that need replay.
Kafka: a durable, replayable log
Kafka's unit is the partitioned topic. Events with the same key go to the same partition and are read in the order written, and consumer groups track their own offsets; Apache Kafka explained shows this in detail. Its strengths follow from the log: very high throughput, long retention, replay for new consumers and reprocessing, and a large ecosystem of connectors and stream processors. Its costs: no per-message routing logic inside the broker, ordering only within a partition, partition counts that must be planned, and more operational weight than a simple queue.
Pulsar: log and queue with separated storage
Pulsar aims to cover both models. It is publish-subscribe, with four subscription types: exclusive and failover for ordered, log-style reading, shared for queue-style round-robin delivery across consumers, and key_shared, which delivers messages with the same key to one consumer (Pulsar docs: messaging). By default acknowledged messages are deleted, as in a queue, and a retention policy keeps them, as in a log.
Its architecture also differs. Brokers are stateless and serve messages, while Apache BookKeeper stores them, with a separate metadata store; multi-tenancy and geo-replication are built in (Pulsar docs: architecture). Separating serving from storage lets each scale independently. The price is more components to run and a smaller community and ecosystem than Kafka's.
Side by side
| RabbitMQ | Kafka | Pulsar | |
|---|---|---|---|
| Model | Queue with exchange routing | Partitioned log | Log with queue-style subscriptions |
| After consumption | Message removed on acknowledgement | Event retained by policy | Removed by default; retained if configured |
| Replay | Only with streams | Yes, by offset | Yes, with retention |
| Ordering | Per queue; affected by redelivery with several consumers | Per partition | Per partition or per key |
| Routing | Rich: direct, fanout, topic, headers | By topic and key | By topic; subscription type decides delivery |
| Typical use | Task queues, request routing between services | Event streaming, data pipelines, analytics | Mixed queueing and streaming, multi-tenant platforms |
How to choose
- Background jobs and service-to-service tasks, with routing rules and per-message retries: RabbitMQ.
- Event streams feeding analytics, several consumers per event, replay and high volume: Kafka. It is also the default for data engineering, so skills and tooling are plentiful; building a streaming pipeline with Kafka and Spark shows a typical use.
- One platform for both queueing and streaming, many tenants, or geo-replication as a core need: evaluate Pulsar.
- Already running one of them well? Staying put is usually right. Each can cover part of the others' ground, and operating two messaging systems costs more than most feature gaps.
Whichever you choose, delivery is normally at-least-once, so consumers must tolerate duplicates. How the choice fits a wider low-latency design is covered in real-time analytics: architecture and tools.
Is Kafka a replacement for RabbitMQ?
Can RabbitMQ replay messages?
What makes Pulsar different from Kafka?
Which is best for data engineering?
Apache Kafka basics, stream processing concepts and event-driven architecture are part of the streaming module of the Data Engineering course.
Sources
- Introduction (Apache Kafka documentation) — Apache Kafka
- Queues (RabbitMQ documentation) — RabbitMQ
- AMQP 0-9-1 model explained (RabbitMQ documentation) — RabbitMQ
- Architecture overview (Apache Pulsar documentation) — Apache Pulsar
- Messaging concepts (Apache Pulsar documentation) — Apache Pulsar
GloryTecks Team
Training & Placement Faculty
The GloryTecks faculty team mentors learners across Data Science, AI and Data Engineering, with a focus on real-time projects and placement readiness in Hyderabad.
Want to master Data Engineering?
Join GloryTecks’ expert-led Data Engineering training in Hyderabad with real-time projects and 100% placement support.
Related articles
Data Engineering Roadmap 2026: A Complete Guide
A stage-by-stage data engineering roadmap for 2026: what the role involves according to current certification guides, then seven stages, SQL, Python, pipelines and modelling, orchestration and quality, Spark and big data, streaming, and cloud platforms, each with a checkpoint, and detours to avoid.
ETL vs ELT: Differences and When to Use Each
The difference between ETL and ELT, shown by running both on three million real taxi trips: ETL stores 31 transformed rows while ELT keeps every raw row and transforms in SQL, then a business rule changes and only ELT can re-derive without re-extracting; plus when to choose each, reverse ETL and streaming.
Data Warehouse Explained: Concepts and Architecture
A clear explanation of data warehouses: why analytical queries need a separate store from transactional systems, the architecture from sources through staging, modelled layers and a semantic model, facts, dimensions, grain and history, modelling approaches, modern platforms and common pitfalls.