# bunqueue vs BullMQ Benchmark: Redis 8.10.2, BullMQ 6.4.2

bunqueue 2.9.20 against BullMQ 6.4.2 on Redis 8.10.2: enqueue and drain throughput, open-loop pickup latency, delayed jobs, retries and memory, in memory and on disk, with the machine, method and raw runs.

Canonical: https://bunqueue.dev/guide/bullmq-benchmark/

---

<!-- Generated by bench/vs-bullmq/src/page.ts from docs/src/data/benchmark-vs-bullmq.json. Do not edit by hand. -->

bunqueue 2.9.20 against BullMQ 6.4.2 on Redis 8.10.2, on one machine with pinned cores, 5 measured runs per number and an exact-delivery check on every run. Where each one is faster, and by how much.

Measured October 25, 2026. Every table cell is the median of 5 runs after one warm-up run, with the range of the 5 runs in parentheses. BullMQ runs on both Node.js and Bun, and each ratio compares bunqueue with the faster of the two. Pickup-latency and delayed-job rows use bunqueue with `pollTimeout: 5000`, the long-poll mode BullMQ workers use by default; every other bunqueue row uses its defaults.

## Machine and versions

| | |
| --- | --- |
| CPU | AMD Ryzen 9 7950X3D, 16 cores / 32 threads, up to 5.76 GHz, 128 MiB L3 (two CCDs) |
| Memory | 124 GiB |
| Disk | 3x Samsung PM9A3 NVMe (MZQL21T9HCJR) in RAID 5, ext4 |
| System | Debian GNU/Linux 13 (trixie), Linux 6.12.48+deb13-amd64, bare metal, CPU governor performance |
| Pinning | broker on CPUs 2,3,18,19 (two physical cores), clients on 8-15,24-31 (the other CCD) |
| Conditions | a shared server: other services ran during the run, so each cell reports the median of five runs with its range |
| bunqueue | 2.9.20 (npm), server started with `bunqueue start`, clients from `bunqueue/client` on Bun 1.4.4 |
| BullMQ | 6.4.2 with ioredis 6.0.0, on Node.js 24.22.1 and on Bun 1.4.4 |
| Redis | 8.10.2, official Docker image, host networking |

## How it was measured

- One broker at a time, Redis or the bunqueue server, pinned to the same two physical cores. Producers and workers run in separate processes on the other core complex, so the broker never shares a core or a cache with its clients.
- Both libraries use their documented defaults, plus `removeOnComplete` and `removeOnFail` on every job. The bunqueue server runs with `RATE_LIMIT_WINDOW_MS=0`: its per-connection request limit protects a shared server and Redis has no equivalent.
- Every run uses a fresh client process and a new queue. Every consuming run checks that each accepted job was processed exactly once (three times in the retry scenario); a run that loses or duplicates a job fails instead of reporting a number.
- Pickup latency comes from an open-loop producer at a fixed rate and is measured from each job's *scheduled* send time, so a broker that falls behind shows up as latency rather than as a lower offered load.
- In memory: bunqueue's in-memory engine against Redis without persistence. Durable: bunqueue on SQLite (WAL) against Redis with AOF and `appendfsync everysec`, both on the same disk.

## In memory

| Scenario | bunqueue | BullMQ on Node.js | BullMQ on Bun | bunqueue vs best BullMQ |
| --- | --- | --- | --- | --- |
| Add jobs one at a time (jobs/s) | 28,890 (27,036–29,538) | 19,450 (18,927–19,534) | 22,730 (22,366–23,231) | **1.27x** |
| Add jobs, 64 in flight (jobs/s) | 202,072 (198,734–219,865) | 68,094 (67,780–70,096) | 77,692 (75,974–79,439) | **2.60x** |
| Add jobs in bulk (100 per call) (jobs/s) | 207,601 (186,591–217,414) | 47,831 (47,232–48,015) | 52,503 (51,567–53,303) | **3.95x** |
| Process a backlog, 1 worker process (jobs/s) | 43,335 (43,240–45,233) | 38,402 (38,314–38,429) | 38,262 (38,171–38,306) | **1.13x** |
| Process a backlog, 4 worker processes (jobs/s) | 53,754 (52,417–53,920) | 40,699 (40,653–40,714) | 39,879 (39,837–40,653) | **1.32x** |
| Process a backlog, 8 worker processes (jobs/s) | 42,408 (41,534–42,429) | 39,873 (39,856–39,874) | 39,830 (39,814–39,857) | **1.06x** |
| Pickup latency p50 at 1,000 jobs/s (ms, lower is better) | 0.65 ms (0.64–0.65) | 0.78 ms (0.78–0.78) | 0.70 ms (0.70–0.71) | **1.09x** |
| Pickup latency p99 at 1,000 jobs/s (ms, lower is better) | 1.18 ms (1.18–1.19) | 1.35 ms (1.35–1.36) | 1.25 ms (1.25–1.25) | **1.06x** |
| Pickup latency p50 at 5,000 jobs/s (ms, lower is better) | 0.81 ms (0.80–0.82) | 1.02 ms (1.01–1.03) | 0.88 ms (0.88–0.88) | **1.08x** |
| Pickup latency p99 at 5,000 jobs/s (ms, lower is better) | 2.68 ms (2.50–2.96) | 1.56 ms (1.55–1.57) | 1.37 ms (1.36–1.38) | **BullMQ 1.96x** |
| Pickup latency p50 at 10,000 jobs/s (ms, lower is better) | 0.94 ms (0.93–0.94) | 1.41 ms (1.40–1.41) | 1.14 ms (1.14–1.14) | **1.22x** |
| Pickup latency p99 at 10,000 jobs/s (ms, lower is better) | 4.29 ms (4.10–5.11) | 2.22 ms (2.21–2.24) | 1.84 ms (1.80–1.87) | **BullMQ 2.33x** |
| Delayed job lateness p50 (ms, lower is better) | 4.74 ms (4.54–4.86) | 40.5 ms (40.2–40.6) | 41.0 ms (40.9–41.5) | **8.54x** |
| Delayed job lateness p99 (ms, lower is better) | 13.1 ms (12.9–13.7) | 83.1 ms (82.9–83.4) | 83.7 ms (83.3–84.0) | **6.34x** |
| Retries (fail twice, succeed on the third attempt) (jobs/s) | 7,119 (7,118–7,129) | 4,243 (4,192–4,284) | 5,536 (5,392–5,544) | **1.29x** |

**bunqueue ahead** (1.1x or more):

- Add jobs one at a time: **1.27x**
- Add jobs, 64 in flight: **2.60x**
- Add jobs in bulk (100 per call): **3.95x**
- Process a backlog, 1 worker process: **1.13x**
- Process a backlog, 4 worker processes: **1.32x**
- Pickup latency p50 at 10,000 jobs/s: **1.22x**
- Delayed job lateness p50: **8.54x**
- Delayed job lateness p99: **6.34x**
- Retries (fail twice, succeed on the third attempt): **1.29x**

**Level** (within 10%):

- Process a backlog, 8 worker processes: **1.06x**
- Pickup latency p50 at 1,000 jobs/s: **1.09x**
- Pickup latency p99 at 1,000 jobs/s: **1.06x**
- Pickup latency p50 at 5,000 jobs/s: **1.08x**

**BullMQ ahead:**

- Pickup latency p99 at 5,000 jobs/s: **BullMQ 1.96x**
- Pickup latency p99 at 10,000 jobs/s: **BullMQ 2.33x**

## Durable: SQLite against Redis AOF

| Scenario | bunqueue | BullMQ on Node.js | BullMQ on Bun | bunqueue vs best BullMQ |
| --- | --- | --- | --- | --- |
| Add jobs one at a time (jobs/s) | 9,000 (8,924–9,016) | 17,785 (17,661–18,065) | 20,316 (19,738–20,920) | **BullMQ 2.26x** |
| Add jobs, 64 in flight (jobs/s) | 31,773 (31,044–33,009) | 62,238 (61,957–62,672) | 63,431 (63,234–63,880) | **BullMQ 2.00x** |
| Add jobs in bulk (100 per call) (jobs/s) | 41,913 (41,137–42,482) | 40,762 (40,464–40,966) | 44,285 (43,834–45,235) | **BullMQ 1.06x** |
| Process a backlog, 1 worker process (jobs/s) | 4,946 (4,874–5,150) | 32,201 (32,180–32,226) | 31,114 (31,070–32,136) | **BullMQ 6.51x** |
| Process a backlog, 4 worker processes (jobs/s) | 4,491 (4,451–4,521) | 33,242 (33,225–33,260) | 33,219 (33,216–33,259) | **BullMQ 7.40x** |
| Process a backlog, 8 worker processes (jobs/s) | 4,257 (4,234–4,288) | 33,233 (32,716–33,240) | 33,215 (33,196–33,240) | **BullMQ 7.81x** |
| Pickup latency p50 at 1,000 jobs/s (ms, lower is better) | 0.97 ms (0.97–0.98) | 0.80 ms (0.78–0.80) | 0.72 ms (0.71–0.72) | **BullMQ 1.36x** |
| Pickup latency p99 at 1,000 jobs/s (ms, lower is better) | 5.72 ms (5.67–5.78) | 1.38 ms (1.34–1.38) | 1.27 ms (1.27–1.28) | **BullMQ 4.50x** |
| Pickup latency p50 at 5,000 jobs/s (ms, lower is better) | 1612 ms (1579–1618) | 1.11 ms (1.09–1.11) | 0.92 ms (0.92–0.92) | **BullMQ 1,746x** |
| Pickup latency p99 at 5,000 jobs/s (ms, lower is better) | 3590 ms (3548–3669) | 1.65 ms (1.65–1.68) | 1.46 ms (1.44–1.46) | **BullMQ 2,465x** |
| Pickup latency p50 at 10,000 jobs/s (ms, lower is better) | 13117 ms (13021–13183) | 1.56 ms (1.55–1.58) | 1.24 ms (1.23–1.24) | **BullMQ 10,604x** |
| Pickup latency p99 at 10,000 jobs/s (ms, lower is better) | 22910 ms (22784–22959) | 2.77 ms (2.74–2.96) | 2.70 ms (2.62–2.96) | **BullMQ 8,476x** |
| Delayed job lateness p50 (ms, lower is better) | 644 ms (623–666) | 39.2 ms (39.0–39.5) | 39.5 ms (39.3–39.5) | **BullMQ 16.4x** |
| Delayed job lateness p99 (ms, lower is better) | 1181 ms (1151–1221) | 82.0 ms (81.7–82.7) | 81.6 ms (81.0–81.9) | **BullMQ 14.5x** |
| Retries (fail twice, succeed on the third attempt) (jobs/s) | 809 (806–819) | 4,074 (4,047–4,141) | 4,985 (4,978–4,989) | **BullMQ 6.16x** |

On SQLite, bunqueue processes about 4,946 jobs/s with one worker process. At 5,000 and 10,000 jobs/s the offered load is above that, so the backlog grows for the whole 15-second run and the pickup-latency rows measure how far behind the queue fell, not a steady-state latency.

The disk matters for this mode. This server's disk is a RAID 5 array of three NVMe drives. In a diagnostic single run on the same server (one worker process, 50,000 jobs), the one-process drain reached 6,165 jobs/s with the SQLite file on that disk and 11,332 jobs/s with it in RAM (`/dev/shm`): the disk explains about half of the gap, and Redis with AOF `everysec` stays ahead even then. Redis appends to its log and syncs once a second; bunqueue's SQLite engine writes each completed job, with `synchronous = NORMAL` in WAL mode.

**bunqueue ahead** (1.1x or more):

- none

**Level** (within 10%):

- Add jobs in bulk (100 per call): **BullMQ 1.06x**

**BullMQ ahead:**

- Add jobs one at a time: **BullMQ 2.26x**
- Add jobs, 64 in flight: **BullMQ 2.00x**
- Process a backlog, 1 worker process: **BullMQ 6.51x**
- Process a backlog, 4 worker processes: **BullMQ 7.40x**
- Process a backlog, 8 worker processes: **BullMQ 7.81x**
- Pickup latency p50 at 1,000 jobs/s: **BullMQ 1.36x**
- Pickup latency p99 at 1,000 jobs/s: **BullMQ 4.50x**
- Pickup latency p50 at 5,000 jobs/s: **BullMQ 1,746x**
- Pickup latency p99 at 5,000 jobs/s: **BullMQ 2,465x**
- Pickup latency p50 at 10,000 jobs/s: **BullMQ 10,604x**
- Pickup latency p99 at 10,000 jobs/s: **BullMQ 8,476x**
- Delayed job lateness p50: **BullMQ 16.4x**
- Delayed job lateness p99: **BullMQ 14.5x**
- Retries (fail twice, succeed on the third attempt): **BullMQ 6.16x**

## Broker memory with a 1,000,000-job backlog

| Mode | bunqueue idle | bunqueue with 1M jobs | Redis idle | Redis with 1M jobs |
| --- | --- | --- | --- | --- |
| In memory | 46 MiB | 2252 MiB | 24 MiB | 618 MiB |
| Durable | 49 MiB | 2415 MiB | 24 MiB | 583 MiB |

Resident memory of the broker process (bunqueue) or `used_memory_rss` (Redis), read after the backlog was added.

## Reproduce it

The harness, its pinned versions and the raw results of this run are in [`bench/vs-bullmq`](https://github.com/egeominotti/bunqueue/tree/main/bench/vs-bullmq) in the repository:

```bash
cd bench/vs-bullmq
bun install --frozen-lockfile
bun src/run.ts
```

It needs Linux, Docker, Bun, Node.js and `taskset`, and takes about 70 minutes. Results land in `results/<timestamp>/`: every run, the medians, and the hardware, kernel and package versions. The [comparison guide](https://bunqueue.dev/guide/comparison/) covers features and when BullMQ is the better choice.
