BullMQ
BullMQ provides background-job queues and workers for Node.js. Design each job around a repeat-safe business operation, then configure scheduling, retries, concurrency, and recovery around that operation. The queue tracks attempts; the application still owns the meaning of the side effect.
Separate processor logic from queue infrastructure so both can be tested at the right boundary. A function that returns the expected value is useful evidence, but it does not prove Redis connectivity, lock renewal, stalled-job recovery, or durable duplicate protection.
TL;DR
- Give jobs a stable business identity and a deliberate repeat-safety rule.
- Throw proper errors for failed processing and bound retry/backoff policy.
- Keep concurrency and rate limiting distinct.
- Avoid blocking the event loop that maintains worker coordination.
- Test real queue lifecycle and failure recovery separately from pure processor logic.
Quick Example
This complete CommonJS example checks an offline processor and installed BullMQ exports. It targets BullMQ 6. It creates no Queue or Worker and contacts no Redis server. The fictional job has no external side effect.
In an authorized local Redis integration, wire this processor into a Worker on a unique test queue, submit through Queue, observe completion/failure, and close both clients in guaranteed cleanup.
Core Concepts
Job, queue, and worker
A producer submits a named job with data and options. A worker claims jobs and runs the processor. Treat job data as an input boundary: validate types, version the payload contract, and avoid embedding credentials or large private records when a protected reference is sufficient.
Attempts and backoff
BullMQ supports bounded attempts and backoff strategies such as fixed and exponential delays. An attempts value counts the original execution as well as permitted repeats. Choose policy by failure type and business semantics; a malformed job will not improve with repeated execution. Retry behavior
Idempotency
A stable job identifier can help submission deduplication within its documented lifetime, but it does not make an external payment, email, or database write exactly once. Persist the business-operation identity with the side effect or use the target’s supported idempotency mechanism, including its expiry and failure semantics.
Match concurrency, rate limits, and flows to the workload
Concurrency controls how much work can be active; rate limiting controls admission over time. They address different constraints. A slow downstream API may need both a small active set and a rate budget. BullMQ documents a queue-wide worker rate limiter; verify the version/backend scope and any additional product-specific features before depending on them. Rate limiting
Flows express parent/child job dependencies. Define whether a failed child blocks, fails, or follows another supported policy for the parent; do not infer a distributed transaction from a dependency tree. A completed child may already have committed an external effect. Flows
Version payloads when producers and workers can roll out separately. Test an old payload with a new worker and a new payload against the supported overlap policy. A queue can preserve work long enough for version mismatches to appear after deployment.
Rehearse stalled and interrupted work
Worker coordination depends on maintaining job locks. BullMQ’s stalled-job guidance warns that blocking the Node event loop can prevent timely coordination and lead to recovery behavior. Move suitable CPU-heavy work away from the coordination loop and test the actual workload. Stalled jobs
In an isolated integration environment, interrupt a worker after a fictional external effect but before completion acknowledgment. Verify the recovered attempt does not duplicate the business effect. Also test Redis interruption, job timeout policy, shutdown, and delayed retry. Preserve attempt history and the stable operation identifier.
Monitor queue age, waiting/active/failed counts, completion rate, retries, and stalled events together. A low waiting count is not reassuring if work is rapidly failing. Keep retained job data and error messages within the project’s access and retention policy.
No Redis worker, retry scheduler, rate limiter, flow, or stalled recovery was executed for the offline example. Use those explicit integration checks before accepting the service configuration.
Best Practices
Keep the business effect repeat-safe
Test the effect boundary, including lost acknowledgments. A function-level mock cannot establish the atomicity of a database write or a third-party API.
Bound work and retries
Set workload-appropriate attempts, delays, deadlines, and admission limits. Count logical jobs separately from processing attempts when interpreting throughput.
Close and recover deliberately
Use documented graceful shutdown and client cleanup. Test interruption as well as graceful completion, because production processes do not always get a polite exit.
Common Mistakes
Bad: Assuming a job ID guarantees exactly-once side effects forever.
Correct: Review deduplication lifetime and implement repeat safety at the actual business effect.
Bad: Increasing concurrency to solve a downstream rate limit.
Correct: Control both active work and request rate, then verify behavior against the dependency.
Bad: Claiming the offline processor proves stalled recovery.
Correct: Run a real isolated queue/worker interruption exercise and retain its observed results.
FAQ
Was Redis tested here?
No. The example validates pure processor behavior and installed exports only.
Should every failure be retried?
No. Separate invalid/permanent failures from eligible transient failures and preserve business repeat safety.
Are flows transactions?
A dependency graph does not undo already completed external effects. Define compensation or reconciliation explicitly when the business process needs it.
Related Topics
- Background Jobs & Workers — Design asynchronous work lifecycles.
- Queueing Theory and Saturation — Understand backlog and admission.
- Error Taxonomy and Failure Recovery — Classify safe recovery actions.