1. Omid Sayfun
  2. /
  3. Notebook
  • Home
  • About
  • Notebook
  • Token Usage
  • Whisper Usage
Tools
  • Agent Knowledge Base

Kafka vs RabbitMQ

May 08, 2026 · Updated on August 09, 2026

The Kafka vs RabbitMQ comparison looks like a question about protocol, throughput, message ordering, and delivery guarantees. It is really a question about one thing: what happens to a message after a consumer reads it.

In RabbitMQ, the message is gone after it's consumed. In Apache Kafka, it stays, for hours, days, or indefinitely. That one difference drives almost every other tradeoff between the two.

I write short, practical notes like this one. Get the next one by email:

Unsubscribe anytime.

Kafka keeps messages, RabbitMQ deletes them

RabbitMQ is a message broker. A producer sends a message. RabbitMQ routes it to one or more consumers via exchanges and queues. Once a consumer acknowledges the message, it's gone. The broker's job is delivery.

Kafka is a distributed log. A producer appends an event to a topic. The event stays there - for hours, days, or indefinitely, depending on your retention config. Each consumer tracks its own position in the log (its offset). Consumers can read from any point, replay from the beginning, or pick up exactly where they left off after a crash.

That's the whole comparison. Everything else - throughput numbers, ecosystem, operational weight - flows from this.

Five questions decide the choice

Do you need replay? If yes, use Kafka. RabbitMQ deletes messages after consumption. Kafka keeps them. Replay matters when you're rebuilding a read model, debugging production events, onboarding a new service that needs historical data, or recovering from a processing bug.

Do you need to fan out to many independent consumers? Kafka handles this naturally - each consumer reads the log at its own pace with no extra configuration. With RabbitMQ, fan-out means either a fanout exchange (all queues get everything) or separate queues per consumer. Each copy is stored independently.

Do you need complex routing? RabbitMQ's exchange types - direct, topic, headers, fanout - give you flexible per-message routing with very little code. Kafka routes by topic only. Fine-grained routing in Kafka ends up in application code or a stream processing layer.

What throughput do you actually need? RabbitMQ handles 20-50K messages/second per node comfortably. Kafka handles 1M+ per broker. Below that range, Kafka vs RabbitMQ performance is not the deciding factor: RabbitMQ's throughput is more than enough, and you'll never notice the difference.

Do you just need a job queue? If you're sending emails, processing file uploads, or running background tasks in a Node.js app, use BullMQ. It's Redis-backed, handles retries, delays, and rate limiting out of the box, and adds no new infrastructure if you're already on Redis. BullMQ vs Kafka is not a close call here: Kafka needs a cluster, BullMQ needs only the Redis you already run. In a BullMQ vs RabbitMQ vs Kafka decision for background jobs, that extra infrastructure is the whole cost, and reaching for either broker is genuine overkill.

Produce and consume in TypeScript

Kafka

KafkaJS used to be the standard Node.js client, but it hasn't been maintained since 2023. The KafkaJS alternative to use now is @confluentinc/kafka-javascript, which wraps the mature librdkafka C library and ships with a KafkaJS-compatible API mode:

import { Kafka } from "@confluentinc/kafka-javascript";
 
const kafka = new Kafka({
	kafkaJS: { brokers: ["localhost:9092"] },
});

The producer sends one message per event, with a key that decides the partition:

const producer = kafka.producer();
await producer.connect();
 
await producer.send({
	topic: "orders",
	messages: [
		{
			key: "order-123",
			value: JSON.stringify({ orderId: "123", amount: 59.99 }),
		},
	],
});
 
await producer.disconnect();

The consumer joins a group, subscribes to the topic, and handles each message:

const consumer = kafka.consumer({ kafkaJS: { groupId: "order-service" } });
await consumer.connect();
await consumer.subscribe({ topic: "orders", fromBeginning: false });
 
await consumer.run({
	eachMessage: async ({ partition, message }) => {
		const order = JSON.parse(message.value!.toString());
		// process order
	},
});

The groupId is important: consumers in the same group split the partitions between them (parallel processing). Consumers in different groups each get all messages independently (fan-out).

RabbitMQ

Using amqplib, which is already well-established in the Node.js ecosystem.

The producer declares a topic exchange and publishes with a routing key:

import { connect } from "amqplib";
 
const connection = await connect("amqp://localhost");
const channel = await connection.createChannel();
 
await channel.assertExchange("orders", "topic", { durable: true });
 
channel.publish(
	"orders",
	"order.placed",
	Buffer.from(JSON.stringify({ orderId: "123", amount: 59.99 })),
	{ persistent: true },
);

The consumer binds its own queue to a routing pattern and acknowledges each message:

const q = await channel.assertQueue("", { exclusive: true });
await channel.bindQueue(q.queue, "orders", "order.*");
 
channel.consume(q.queue, (msg) => {
	if (!msg) return;
	const order = JSON.parse(msg.content.toString());
	// process order
	channel.ack(msg);
});

The routing key "order.*" matches order.placed, order.cancelled, order.refunded - anything in that namespace. This is RabbitMQ's topic exchange doing work that Kafka would push into application code. For more on how exchanges and queues fit together, this breakdown covers the routing model. If you need request-response over RabbitMQ, the RPC pattern with amqplib shows the full correlation ID approach.

RabbitMQ is cheaper to operate than Kafka

RabbitMQ runs as a single node for development and small production workloads. One Docker container, one amqp:// URL. Clustering is optional and well-documented. That simplicity is a real advantage - it's why RabbitMQ is often the default choice for teams that need messaging but not a data platform.

Kafka has historically required running ZooKeeper alongside it - two distributed systems to manage. Kafka 4.0 (released March 2025) removed ZooKeeper entirely. Kafka now runs in KRaft mode only, which manages cluster metadata internally. This is a meaningful simplification, but you're still operating a multi-broker cluster in production. In practice, most teams running Kafka at scale use a managed service: Confluent Cloud, Amazon MSK, or Redpanda Cloud.

Running both is an outcome, not a starting point

Some teams never settle RabbitMQ vs Kafka, and run both. Kafka as the event log, the source of truth for what happened, with replay and fan-out. RabbitMQ for task routing - specific jobs to specific workers, with routing logic and acks. The two can complement each other when a system has genuinely different requirements in different areas.

It's a pattern that emerges at scale, after you've outgrown one tool for specific workloads.

When to use which

Use RabbitMQ when:

  • You need service-to-service messaging with routing logic
  • You want simple operational overhead, especially early on
  • You need request-reply patterns (RPC over AMQP)
  • Throughput is in the tens of thousands per second range

Use Kafka when:

  • You specifically need event replay - rebuilding state, onboarding new consumers to historical data
  • Throughput is in the hundreds of thousands per second range
  • You want an event log as a first-class architectural concept, not just a delivery mechanism

Use BullMQ when:

  • You're on Node.js and need background job processing
  • You're already running Redis
  • You need retries, delays, rate limiting, or a dashboard without rolling it yourself

For most backend services: start with RabbitMQ. It's easier to run, easier to reason about, and fits typical service-to-service communication well. Add Kafka when you hit a concrete requirement it can't meet - usually replay or scale.

Join My Newsletter

Occasional notes on software, tools, and things I learn. No spam.

Unsubscribe anytime.

Continue Reading

  • Embeddings rot too. Running pgvector in production.Aug 28, 2026
  • Stop shipping retrieval changes on vibesAug 25, 2026
  • Your RAG is confidently wrong without hybrid searchAug 21, 2026
  • Stop tuning everything. pgvector has three knobs that matter.Aug 18, 2026
  • Your agent's knowledge base is lying to you. Run these 14 checks.Aug 18, 2026