Traditional Queue vs Apache Kafka #

In the world of distributed system architecture, asynchronous communication is the key to building responsive, failure-resistant systems. To connect various services without making them tightly coupled, we’ve historically used intermediary software known as message brokers or message queues. This technology choice heavily determines how data is sent, stored, and processed. However, there’s a common misconception that all message brokers work the same way. In particular, there are huge fundamental differences between traditional Message Queues (like RabbitMQ or ActiveMQ) and Apache Kafka.

In this article, we’ll take a deep dive into the architectural differences between traditional Message Queues and Apache Kafka. We’ll cover traditional queue concepts (such as the Point-to-Point and Publish/Subscribe models), message delivery mechanisms, the destructive vs non-destructive data lifecycle, and a detailed comparison table to help you pick the right technology for your architecture.

Understanding Traditional Message Queues #

Before Apache Kafka was born, the IT industry had long used Message-Oriented Middleware (MOM) standards such as JMS (Java Message Service) and the AMQP (Advanced Message Queuing Protocol) protocol. Software like RabbitMQ is a highly representative and popular example of this category.

In general, traditional Message Queues support two main communication models:

1. Point-to-Point Model (Queue) #

In this model, the sender (producer) sends a message to a specific queue. The receiver (consumer) reads messages from that queue. The key characteristic of this model is that each message is processed by exactly one consumer.

This pattern is often associated with the Competing Consumers or Worker Pool pattern. If you have heavy task-processing workloads, you can run multiple consumer applications simultaneously listening to the same queue. The broker distributes messages alternately (Round-Robin) or based on each worker’s processing capacity (prefetch count). Once a message is picked up by one consumer, the other consumers will never see it.

flowchart LR
    P["Producer"] -->|Send Message| Q[("Queue")]
    Q -->|Message 1| CA["Consumer A"]
    Q -->|Message 2| CB["Consumer B"]

2. Publish/Subscribe Model (Topic) #

This model is used when a single message needs to be delivered to many different receivers at the same time for different purposes. For example, when an order is created, the order data must be sent to the inventory system, the payment system, and the notification system all at once.

The sender publishes a message to a Topic (or Exchange in RabbitMQ). Every interested consumer creates their own private queue and binds it to the topic using a matching rule (routing key). The broker then physically duplicates the message and inserts it into every bound consumer queue. This pattern is known as the Fan-out mechanism. If there are three bound queues, the broker has to copy the data three times, once into each queue.

flowchart LR
    P["Producer"] -->|Send Message| Ex{{"Topic (Exchange)"}}
    Ex -->|Copy Message| QA[("Queue A")] --> CA["Consumer A"]
    Ex -->|Copy Message| QB[("Queue B")] --> CB["Consumer B"]
    Ex -->|Copy Message| QC[("Queue C")] --> CC["Consumer C"]

Key Characteristic: Destructive Read and Push Model #

Traditional Message Queues have one very distinctive physical characteristic: Destructive Read. After a message is successfully read and acknowledged by a consumer, it’s immediately deleted from the broker’s memory or storage. Traditional brokers are designed to keep their queues as empty as possible. If queues pile up and get very long, traditional broker performance usually drops drastically due to RAM memory constraints.

Additionally, most traditional brokers use the Push Model. The broker actively tracks consumer status and pushes messages directly to active consumers over open network connections. The broker must maintain strict connection state and acknowledgment status information for every message of every consumer.


Understanding Apache Kafka’s Distributed Log Model #

Apache Kafka throws away the traditional queue concept and replaces it with a very simple data structure: a distributed Append-Only Log. In Kafka, when a producer sends data to a topic, that data isn’t placed into a queue that will be deleted right after being read. Instead, it’s written sequentially to the end of a physical log file on disk (append-only).

flowchart LR
    subgraph Log["Kafka Topic (Append-Only Log)"]
        direction LR
        P0["Message 0"] --> P1["Message 1"] --> P2["Message 2"] --> P3["Message 3"] --> P4["New Message 4"]
    end
    CA["Consumer A"] -. Offset 1 .-> P1
    CB["Consumer B"] -. Offset 3 .-> P3

Key Characteristic: Non-Destructive Read and Pull Model #

Because Kafka uses a log model, reading data from Kafka is Non-Destructive. Consumer clients that read messages from Kafka don’t delete those messages from the broker. The messages stay permanently on disk until the configured retention period passes (for example, 7 days or even forever).

Consumers track their own read position using a simple numeric pointer called an Offset. If two different consumers want to read data from the same topic, each only needs to manage their own offset pointer. Consumer A can read from the beginning of history (offset 0), while Consumer B reads the latest data (offset 100). Kafka doesn’t need to duplicate that data in physical storage. This makes data replayability extremely easy; if there’s a bug in the consumer application, you just rewind the consumer’s offset pointer to the past and re-run the application.

Kafka also uses the Pull Model. Kafka brokers are passive; they never push data to consumers. It’s the consumers who periodically pull batches of data from the broker according to their own processing capacity. This prevents consumers from being overwhelmed (backpressure) when there’s a huge data surge from the producer side.


Detailed Comparison Table #

To give you a more systematic picture, let’s look at the comparison table between traditional Message Queues and Apache Kafka below:

CriteriaTraditional Message Queue (e.g., RabbitMQ)Apache Kafka
Delivery ModelPush: Broker pushes messages to active consumers. Clients don’t need to request data; they react as soon as data arrives.Pull: Consumers pull messages from the broker at their own pace. Great for preventing overload (backpressure).
Data LifecycleEphemeral (Destructive): Messages are deleted as soon as processing is acknowledged to save RAM.Persistent (Non-Destructive): Messages are stored on disk and not deleted after being read, allowing repeated reads.
Replay CapabilityNone: Processed messages are gone forever from the broker. No historical data trail.Yes: Consumers can rewind offsets to re-read old data if a processing error occurs.
ScalabilityVertical (Limited): Harder to scale horizontally due to the complexity of syncing queue state across servers.Horizontal (Very High): Instant scalability by splitting topics into partitions spread across many brokers.
ThroughputModerate: Tens of thousands of messages per second per queue (limited by queue state management in RAM).Very High: Millions of messages per second (optimized via Sequential IO, Page Cache, and Zero-Copy).
Ordering GuaranteeWeak: Order can break if a failed message is redelivered and re-enters the queue behind other messages.Strong: Order is guaranteed 100% consistent within the same partition level, even after connection failures.
Message RoutingComplex: Supports advanced dynamic routing using wildcard patterns, header matching, and flexible exchanges.Simple: Direct topic-based routing and partition mapping using message keys.

Difference in Internal Operation: RAM vs Disk #

Another fundamental difference is how these two technologies treat their hardware, especially RAM and physical storage (disk). This difference is also influenced by the platform runtime each technology runs on.

Traditional Message Queue (RAM-Oriented, Erlang Runtime) #

For example, RabbitMQ is written in the Erlang programming language and runs on the Erlang VM (BEAM). Erlang excels at handling concurrency of millions of lightweight processes in parallel. RabbitMQ designs its queues with the assumption that consumers will process messages quickly. Therefore, messages are ideally kept in RAM for instant access.

However, if consumers are slow or die, queues start piling up. When RAM usage exceeds a certain threshold (high watermark), the Erlang VM is forced to Page-Out, moving message data from RAM to disk to prevent running out of memory. This random disk I/O heavily burdens the Erlang runtime, temporarily halts new message acceptance, and causes broker performance to drop off a cliff.

Apache Kafka (Disk-Oriented, JVM Page Cache) #

Kafka is written in Java and Scala and runs on the JVM (Java Virtual Machine). Putting gigabytes of data into JVM Heap memory is a recipe for instant disaster because it triggers long pauses when the JVM performs Garbage Collection (GC pause).

Kafka cleverly avoids this by storing all data directly to disk as a sequential log. Kafka doesn’t use RAM to store Java objects; instead, it lets the Linux operating system manage it as a Page Cache. Because writes are always sequential, the OS can predict the next reads very accurately (read-ahead) and write data efficiently (write-behind). As a result, Kafka’s disk I/O performance stays consistently high whether the data in a topic is megabytes or terabytes.


Design Anti-Patterns in the Industry #

Understanding the differences above is crucial so we don’t make mistakes in choosing an architecture. Let’s look at two common cases of technology misplacement we often see in the industry, along with the consequences of failure:

Anti-Pattern 1: Using Kafka as a Complex Task Queue #

A team wants to build a background job processor where each task has a different priority and requires highly dynamic routing (for example: “Send task A to worker X, send task B to worker Y based on message headers”). They try to use Kafka for this.

Consequences: Because Kafka doesn’t support individual message deletion or dynamic message routing on the broker side, the team is forced to write very complex custom code on the consumer side to filter messages. Network bandwidth is wasted because all consumers receive messages irrelevant to them, and processing performance drops due to high CPU consumption for data filtering.

# ANTI-PATTERN: Trying to simulate dynamic message routing in Kafka
# Kafka is not designed for individual message-level routing inside the broker.
# You're forced to create dozens of custom topics or filter messages manually on the client side,
# wasting network bandwidth and CPU resources.

# The CORRECT solution:
# Use RabbitMQ because its Exchange with Direct/Topic/Headers types is designed
# specifically to handle very complex message routing logic on the broker side.

Anti-Pattern 2: Using RabbitMQ for a Log Analytics Pipeline #

A team wants to collect user activity log data from thousands of web servers for analysis using AI. The data volume reaches 500,000 events per second. They choose RabbitMQ because they’re already familiar with it.

Consequences: Due to the massive data volume, RabbitMQ’s server RAM quickly runs out. Analytics consumers sometimes lag, making RabbitMQ queues swell. Once queues pile up, RabbitMQ performance drops off a cliff due to Page-Out. Eventually the broker server crashes from running out of memory (Out Of Memory), losing important log data that hadn’t been sent yet.

# ANTI-PATTERN: Using RabbitMQ for high-volume streaming data
# RabbitMQ tries to keep queue state in RAM, which collapses under massive log loads.

# The CORRECT solution:
# Use Kafka. Its asynchronous nature, pull model, and disk-based storage
# make it very resilient to absorbing analytics log surges of any size without crashing.

When to Choose Which? #

To help you make architecture decisions quickly, here’s a simple guide based on your system’s needs:

Choose a Traditional Message Queue (RabbitMQ) if: #

  1. You Need Complex Routing: Your application needs dynamic routing based on certain criteria (such as routing key patterns, header matching, etc.).
  2. Message-Level Transactional Guarantees: You need 2-Phase Commit (2PC) or very detailed delivery confirmations for each individual message.
  3. Task Queue Logic: You want to fairly dispatch tasks to multiple workers (fair dispatch), and those tasks should disappear from the broker as soon as they’re done.
  4. Standard Protocols: Your system must integrate with legacy software that only supports AMQP, MQTT, or STOMP protocols.

Choose Apache Kafka if: #

  1. Event Streaming and Real-Time Analytics: You want to process continuously flowing data (like user clicks, IoT sensor data, financial transaction logs).
  2. High-Throughput & Horizontal Scalability: Your system must handle millions of events per second with efficient infrastructure costs.
  3. You Need Replay: You want multiple different consumer applications to independently read data from the same point in the past.
  4. Event Sourcing / CQRS: Your application adopts a pattern of storing state changes as a chronological, persistent history of events.

Summary #

  • Destructive vs Non-Destructive — Traditional Message Queues delete messages as soon as they’re consumed (destructive read), while Kafka keeps data on disk for a specified time (non-destructive read).
  • Push vs Pull — Traditional brokers push data to consumers (push), while Kafka consumers actively pull data (pull) to avoid processing overload (backpressure).
  • RAM vs Disk — Traditional broker performance degrades when RAM fills up and data is forced to disk. In contrast, Kafka is optimized from the start for sequential disk writes using the OS Page Cache.
  • Use Case Fit — Use RabbitMQ for task queue management with complex routing logic. Use Kafka for high-speed analytics data pipelines, stream processing, and distributed event-driven architectures.

← Previous: What is Kafka? Next: Alternatives →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact