bunqueue v2.8.21
Ships 2 performance improvements. Temporal index comparator was not a total order (src/domain/queue/temporalManager.ts).
To install bunqueue v2.8.21
Section titled “To install bunqueue v2.8.21”bun add bunqueue@2.8.21npm install bunqueue@2.8.21To upgrade bunqueue
Section titled “To upgrade bunqueue”bun add bunqueue@latestPerformance
Section titled “Performance”Bulk job insertion (addBulk / PUSHB) degraded super-linearly with batch size, a single 5,000-job batch dropped to ~5k ops/s while embedded mode stayed flat. Profiling root-caused it to two independent O(n²) hot paths, both fixed:
-
Temporal index comparator was not a total order (
src/domain/queue/temporalManager.ts). The cleanup index is aSkipListordered bycreatedAtwith jobId-based deduplication. In a bulk pushnowis captured once, so every job in the batch shares the samecreatedAt, making every node compare-equal, which (a) turnedSkipList.insert’s duplicate-check scan into O(n) per insert ⇒ O(n²) per batch, and (b) madeSkipList.deleteremove the WRONG same-createdAtnode (it stopped at the first compare-equal node, a latent correctness bug inremoveFromIndex). Fixed with a total-order comparator(createdAt, then jobId); jobId is a UUIDv7 string, so lexicographic order is a valid total order. Both the insert dedup scan and delete now resolve to the exact(createdAt, jobId)node in O(log n). Repro:test/repro-temporal-onsquared.test.ts(30k same-createdAtinserts: 10,975ms → 27ms, plus a wrong-delete correctness case). -
SSE broadcast did per-event work even with zero clients connected (
src/infrastructure/server/sseHandler.ts). The TCP server subscribes bothwsHandlerandsseHandlerto every job event.wsHandler.broadcastearly-returns when no clients are connected, butsseHandler.broadcastdid not, so everypushedevent still paidJSON.stringify+TextEncoder.encode+ ring-buffer churn and, worst of all,getQueueJobCounts(queue), which is O(queue size + jobIndex size). During a bulk push the broadcast fires once per job after all jobs are already queued ⇒ O(n²), even with no dashboard attached (the common high-throughput case). Fixed by mirroringwsHandler:if (this.clients.size === 0) return;. Behavior is unchanged whenever ≥1 client is connected. Repro:test/repro-sse-broadcast-noclients.test.ts.
Benchmark, TCP addBulk, server in a separate process, same machine, clean DB per run; before/after measured by stashing the two fixes (apples-to-apples). Reusable harness added as bench/tcp-bench.ts:
| batch size | before | after | speedup |
|---|---|---|---|
| 100 | 28,196 ops/s | 63,403 ops/s | 2.2× |
| 1,000 | 18,372 ops/s | 126,410 ops/s | 6.9× |
| 5,000 | 5,276 ops/s | 170,013 ops/s | 32× |
Per-job cost went from super-linear (35 → 54 → 190 µs/job) to flat (~6 µs/job), matching embedded-mode throughput. All three suites green (5,663 unit + 59 TCP suites + 36 embedded suites).
Thanks to 1 contributor!
Section titled “Thanks to 1 contributor!”← bunqueue v2.8.20 · Release on GitHub · Commits since v2.8.20 · Full changelog · All posts