Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Benchmarks

This page compares FireMQ with RabbitMQ on the same machine, under the same limits, using the same load generator. Run bench/run.sh to reproduce it on your hardware; numbers from a laptop are a comparison, not a capacity plan.

Method

Each broker runs in a Docker container limited to 4 CPUs and 2 GiB of memory. The load generators run in a second container, also limited to 4 CPUs, on the same Docker network. Nothing crosses the macOS host's network stack.

Two load generators produce the same workload:

Both work the same way:

  • Every publisher uses publisher confirms with up to 1,000 unconfirmed messages in flight.
  • Every consumer uses a prefetch of 1,000 and acknowledges each message individually.
  • The queue is durable. "Persistent" scenarios publish persistent messages, which both brokers write to disk before confirming; "transient" scenarios publish non-persistent messages.
  • Each message body starts with its send time, so the consumer measures end-to-end latency from publish to delivery.
  • Throughput scenarios publish as fast as confirms allow. Latency scenarios publish a fixed 1,000 messages per second, so latency isn't dominated by queueing.

A fresh broker container starts for every run. Each scenario runs three times for 20 seconds, and the tables show the median run: the run with the median delivered rate. Memory is the container's peak memory use during the run, sampled every 2 seconds with docker stats.

Throughput is the rate at which consumers received messages. When a broker accepts messages faster than it delivers them, the queue grows, and latency then measures mostly time spent waiting in the queue. The results call out those runs.

The recovery test fills a durable queue with 2,000,000 persistent 256-byte messages, stops the broker gracefully, starts it again, and measures how long until the queue holds every message again. It counts messages with a passive queue.declare, which both brokers answer exactly; RabbitMQ's management API refreshes its counts only every 5 seconds. Startup and recovery times include about 0.2 seconds to start the container that measures them.

Results

Measured on November 7, 2026, on an Apple M2 Pro with 10 cores and Docker Desktop 29.4.3. FireMQ ran at commit bf67ac7. RabbitMQ ran from the rabbitmq:4-management image: RabbitMQ 4.4.0 on Erlang/OTP 29. RabbitMQ's numbers come from a run earlier the same evening. FireMQ's runs were repeated after a change that lowered its idle memory, which doesn't affect RabbitMQ. The raw logs and CSV files are in bench/results/2026-11-07.

Throughput

Messages delivered to consumers per second:

ScenarioRabbitMQFireMQ, AMQPFireMQ, FireMQ protocol
Transient, 1 publisher and 1 consumer, 256 B113,000135,000156,000
Persistent, 1 publisher and 1 consumer, 1 KiB79,000134,000174,000
Transient, 4 publishers and 4 consumers, 256 B123,000274,000331,000
Persistent, 4 publishers and 4 consumers, 1 KiB57,000261,000300,000

Driven by the same AMQP client, FireMQ delivered 1.2 to 4.6 times as many messages per second as RabbitMQ. The gap is widest with persistent messages and several publishers, because FireMQ syncs the writes of all publishers together, at most once every 2 ms. The FireMQ protocol adds another 10 to 25 percent.

In the two AMQP scenarios with 4 publishers, FireMQ accepted messages faster than it delivered them:

  • Transient messages: 313,000 published and 274,000 delivered per second.
  • Persistent messages: 283,000 published and 261,000 delivered per second.

The broker was at its 4-CPU limit in both. FireMQ doesn't slow publishers down when consumers fall behind, so the queue grew for the length of the run. RabbitMQ slows publishers in that situation with its credit flow. Over the FireMQ protocol, whose acknowledgments are cumulative and cheaper to process, publishing and delivery stayed level in every scenario.

Latency

End-to-end latency with publishers held at 1,000 messages per second, p50 / p99:

ScenarioRabbitMQFireMQ, AMQPFireMQ, FireMQ protocol
Transient, 256 B0.65 ms / 1.07 ms0.35 ms / 0.63 ms0.39 ms / 0.73 ms
Persistent, 1 KiB0.66 ms / 1.52 ms0.26 ms / 0.71 ms0.24 ms / 0.70 ms

Persistent messages include the wait for the disk: neither broker confirms or delivers them before the write that holds them is synced.

End-to-end latency at full throughput, p50 / p99:

ScenarioRabbitMQFireMQ, AMQPFireMQ, FireMQ protocol
Transient, 1 publisher and 1 consumer, 256 B6.18 ms / 8.39 ms7.21 ms / 8.29 ms6.25 ms / 7.16 ms
Persistent, 1 publisher and 1 consumer, 1 KiB6.34 ms / 12 ms1.78 ms / 3.98 ms0.88 ms / 3.17 ms
Transient, 4 publishers and 4 consumers, 256 B35 ms / 42 ms1.3 s / 2.5 s13 ms / 26 ms
Persistent, 4 publishers and 4 consumers, 1 KiB28 ms / 64 ms833 ms / 1.6 s5.04 ms / 15 ms

At full throughput, latency mostly measures time spent waiting. When the consumer is the bottleneck, it holds up to its prefetch of 1,000 messages that it hasn't processed yet, so latency approaches 1,000 divided by the delivery rate: about 7 ms at 140,000 messages per second. The FireMQ AMQP rows with 4 publishers show the queue that grew during those runs.

Memory

Peak broker memory during the throughput runs:

ScenarioRabbitMQFireMQ, AMQPFireMQ, FireMQ protocol
Transient, 1 publisher and 1 consumer, 256 B254 MiB9 MiB9 MiB
Persistent, 1 publisher and 1 consumer, 1 KiB252 MiB18 MiB20 MiB
Transient, 4 publishers and 4 consumers, 256 B285 MiB204 MiB22 MiB
Persistent, 4 publishers and 4 consumers, 1 KiB302 MiB129 MiB48 MiB

Most of RabbitMQ's memory is the Erlang runtime, which uses about 245 MiB before the first message. FireMQ's memory follows the messages it holds. In the AMQP runs with 4 publishers, that includes the backlog that built up during the run.

Startup and recovery

RabbitMQFireMQ
Ready to accept connections after the container starts2.04 s0.36 s
Memory when idle246 MiB3 MiB
Restart with 2,000,000 queued messages, until every message is back2.40 s1.34 s
Memory holding those messages, before the restart231 MiB156 MiB
Memory holding those messages, after the restart255 MiB124 MiB
Container image456 MB13.8 MB

On startup FireMQ reads each queue's segment files and verifies every record's checksum, so recovery time grows with the amount of queued data on disk. See Durability and recovery.

Reproduce

bench/run.sh          # 20 seconds per run, 3 runs
bench/run.sh 60 5     # longer runs
CPUS=2 MEMORY=1g bench/run.sh

The script builds the FireMQ image from the working tree, builds the load generators for Linux, and writes raw logs and CSV files to bench/results/<date>/. To turn them into the tables on this page, run:

bench/summarize.py bench/results/<date>