- Docs
- Start Here
- Benchmark vs BullMQ
bunqueue vs BullMQ, measured.
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
Section titled “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
Section titled “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
removeOnCompleteandremoveOnFailon every job. The bunqueue server runs withRATE_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
Section titled “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
Section titled “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
Section titled “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
Section titled “Reproduce it”The harness, its pinned versions and the raw results of this run are in bench/vs-bullmq in the repository:
cd bench/vs-bullmqbun install --frozen-lockfilebun src/run.tsIt 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 covers features and when BullMQ is the better choice.