Skip to content
Get started
Get started
bunqueue v2.8.21
View Markdown
blog · release

bunqueue v2.8.21

Ships 2 performance improvements. Temporal index comparator was not a total order (src/domain/queue/temporalManager.ts).

bun add bunqueue@2.8.21
npm install bunqueue@2.8.21
bun add bunqueue@latest

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 a SkipList ordered by createdAt with jobId-based deduplication. In a bulk push now is captured once, so every job in the batch shares the same createdAt, making every node compare-equal, which (a) turned SkipList.insert’s duplicate-check scan into O(n) per insert ⇒ O(n²) per batch, and (b) made SkipList.delete remove the WRONG same-createdAt node (it stopped at the first compare-equal node, a latent correctness bug in removeFromIndex). 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-createdAt inserts: 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 both wsHandler and sseHandler to every job event. wsHandler.broadcast early-returns when no clients are connected, but sseHandler.broadcast did not, so every pushed event still paid JSON.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 mirroring wsHandler: 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 sizebeforeafterspeedup
10028,196 ops/s63,403 ops/s2.2×
1,00018,372 ops/s126,410 ops/s6.9×
5,0005,276 ops/s170,013 ops/s32×

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).


← bunqueue v2.8.20 · Release on GitHub · Commits since v2.8.20 · Full changelog · All posts