api-queue-bullmq
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseBullMQ Patterns
BullMQ 模式
Quick Guide: Use BullMQ (v5.x) for background job processing, task scheduling, and workflow orchestration on top of Redis. Core classes:(adds jobs),Queue(processes jobs),Worker(global event listener),QueueEvents(parent-child job trees). Always pass aFlowProducerobject to every constructor (required in v5). Setconnectionon ioredis connections for Workers. UsemaxRetriesPerRequest: nullfor repeatable/cron jobs (replaces deprecated repeatable API). QueueScheduler was removed in v4 -- its responsibilities are now handled by Workers automatically.upsertJobScheduler
<critical_requirements>
快速指南: 基于Redis,使用BullMQ(v5.x版本)实现后台作业处理、任务调度与工作流编排。核心类:(添加作业)、Queue(处理作业)、Worker(全局事件监听器)、QueueEvents(父子作业树)。必须为每个构造函数传入FlowProducer对象(v5版本必填)。为Worker的ioredis连接设置connection。使用maxRetriesPerRequest: null处理重复/定时作业(替代已废弃的repeatable API)。QueueScheduler在v4版本中已移除——其职责现在由Worker自动处理。upsertJobScheduler
<critical_requirements>
CRITICAL: Before Using This Skill
重要提示:使用本技能前须知
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,, named constants)import type
(You MUST pass a object to every Queue, Worker, QueueEvents, and FlowProducer constructor -- BullMQ v5 throws if connection is missing)
connection(You MUST set on ioredis connections used by Workers -- BullMQ requires infinite retries and throws without this setting)
maxRetriesPerRequest: null(You MUST call on SIGTERM/SIGINT for graceful shutdown -- without it, in-progress jobs become stalled)
await worker.close()(You MUST use for repeatable/cron jobs -- the old option on is deprecated since v5.16.0)
upsertJobSchedulerrepeatqueue.add</critical_requirements>
所有代码必须遵循CLAUDE.md中的项目规范(短横线命名、命名导出、导入顺序、、命名常量)import type
(必须为每个Queue、Worker、QueueEvents和FlowProducer构造函数传入对象——如果缺少连接,BullMQ v5会抛出错误)
connection(必须为Worker使用的ioredis连接设置——BullMQ要求无限重试,缺少该设置会抛出错误)
maxRetriesPerRequest: null(必须在SIGTERM/SIGINT信号触发时调用以优雅关闭——否则,正在处理的作业会陷入停滞状态)
await worker.close()(必须使用处理重复/定时作业——v5.16.0版本起,中的旧选项已废弃)
upsertJobSchedulerqueue.addrepeat</critical_requirements>
Examples
示例
- Core Patterns -- Queue setup, Worker processing, job options, connection factory, graceful shutdown, typed jobs
- Advanced Patterns -- FlowProducer, rate limiting, job scheduling, QueueEvents, concurrency, sandboxed processors
Additional resources:
- reference.md -- Decision frameworks, job option reference, anti-patterns, production checklist
Auto-detection: BullMQ, bullmq, Queue, Worker, QueueEvents, FlowProducer, job queue, background job, worker process, job scheduler, upsertJobScheduler, rate limiter, job priority, job delay, sandboxed processor, repeatable job, cron job, flow producer, parent child jobs
When to use:
- Background processing (email sending, image processing, PDF generation)
- Scheduled/cron jobs (nightly reports, periodic cleanup)
- Workflow orchestration with parent-child job dependencies (FlowProducer)
- Rate-limited API consumption (throttling outbound requests)
- Priority-based job processing (urgent jobs before bulk operations)
- Distributing CPU-intensive work across multiple workers or machines
Key patterns covered:
- Queue and Worker setup with typed job data and return values
- Connection factory with for Workers
maxRetriesPerRequest: null - Job options: delay, priority, attempts, backoff, removeOnComplete/Fail
- Graceful shutdown with on process signals
worker.close() - FlowProducer for parent-child job trees with dependency tracking
- Job Schedulers for repeatable/cron jobs ()
upsertJobScheduler - Rate limiting (global limiter and manual )
Worker.RateLimitError - Concurrency control (local per-worker and global)
- QueueEvents for global event monitoring across all workers
- Sandboxed processors for CPU-intensive work
When NOT to use:
- Simple in-process timers or (no persistence needed)
setTimeout - Real-time pub/sub messaging without persistence (use your pub/sub solution)
- Data that must be processed synchronously within a request-response cycle
- Queues that don't need persistence, retries, or scheduling
<philosophy>
- 核心模式 —— 队列设置、Worker处理、作业选项、连接工厂、优雅关闭、类型化作业
- 进阶模式 —— FlowProducer、速率限制、作业调度、QueueEvents、并发、沙箱处理器
额外资源:
- reference.md —— 决策框架、作业选项参考、反模式、生产环境检查清单
自动检测关键词: BullMQ, bullmq, Queue, Worker, QueueEvents, FlowProducer, job queue, background job, worker process, job scheduler, upsertJobScheduler, rate limiter, job priority, job delay, sandboxed processor, repeatable job, cron job, flow producer, parent child jobs
适用场景:
- 后台处理(邮件发送、图片处理、PDF生成)
- 定时/ cron作业(夜间报表、定期清理)
- 带有父子作业依赖的工作流编排(FlowProducer)
- 速率限制的API调用(限制出站请求)
- 基于优先级的作业处理(紧急作业优先于批量操作)
- 跨多个Worker或机器分配CPU密集型任务
涵盖的核心模式:
- 带类型化作业数据和返回值的Queue与Worker设置
- 为Worker配置的连接工厂
maxRetriesPerRequest: null - 作业选项:延迟、优先级、重试次数、退避策略、完成/失败后自动移除
- 通过进程信号调用实现优雅关闭
worker.close() - 带依赖跟踪的FlowProducer父子作业树
- 用于重复/定时作业的作业调度器()
upsertJobScheduler - 速率限制(全局限制器与手动)
Worker.RateLimitError - 并发控制(单Worker本地限制与全局限制)
- 跨所有Worker的全局事件监控QueueEvents
- 用于CPU密集型任务的沙箱处理器
不适用场景:
- 简单的进程内定时器或(无需持久化)
setTimeout - 无需持久化的实时发布/订阅消息(使用专用的发布/订阅方案)
- 必须在请求-响应周期内同步处理的数据
- 无需持久化、重试或调度的队列
<philosophy>
Philosophy
设计理念
BullMQ is a Redis-backed job queue for Node.js that provides reliable background processing with at-least-once delivery guarantees. The core principle: separate job production from job consumption so your application stays responsive while work happens asynchronously.
Core principles:
- Producers and consumers are decoupled -- Any process can add jobs to a queue; any Worker can process them. This enables horizontal scaling by adding more Workers.
- Jobs are persistent -- Jobs survive process restarts because they live in Redis. A crashed Worker's jobs are picked up by other Workers (or the same Worker after restart).
- At-least-once delivery -- BullMQ guarantees every job is processed at least once. Use idempotent processors to handle the (rare) case of duplicate processing after a stall.
- Fail gracefully with retries -- Configure and
attemptsstrategies so transient failures resolve automatically. Permanently failed jobs move to the failed set for inspection.backoff - Each Queue/Worker/QueueEvents needs its own connection -- BullMQ manages connection state internally. Never share a single ioredis instance across multiple BullMQ classes (except Queues acting only as producers).
<patterns>
BullMQ是一款基于Redis的Node.js作业队列,提供可靠的后台处理,保证至少一次交付。核心原则:将作业生产与作业消费分离,使应用在异步处理任务时保持响应性。
核心原则:
- 生产者与消费者解耦 —— 任意进程都可向队列添加作业;任意Worker都可处理作业。这支持通过添加更多Worker实现水平扩展。
- 作业持久化 —— 作业存储在Redis中,可在进程重启后保留。崩溃的Worker的作业会被其他Worker(或重启后的同一Worker)接手处理。
- 至少一次交付 —— BullMQ保证每个作业至少被处理一次。使用幂等处理器处理停滞作业可能导致的(罕见)重复处理情况。
- 通过重试优雅失败 —— 配置和退避策略,使临时故障自动恢复。永久失败的作业会移至失败集合供检查。
attempts - 每个Queue/Worker/QueueEvents需要独立连接 —— BullMQ内部管理连接状态。切勿在多个BullMQ类之间共享单个ioredis实例(仅作为生产者的Queue除外)。
<patterns>
Core Patterns
核心模式
Pattern 1: Connection Factory
模式1:连接工厂
BullMQ v5 requires an explicit Redis connection on every constructor. Workers need so ioredis retries indefinitely instead of giving up.
maxRetriesPerRequest: nulltypescript
import Redis from "ioredis";
function createBullMQConnection(): Redis {
const url = process.env.REDIS_URL;
if (!url) throw new Error("REDIS_URL is required");
return new Redis(url, { maxRetriesPerRequest: null });
}
export { createBullMQConnection };Why good: satisfies BullMQ's requirement, factory ensures consistent config, environment variable keeps credentials out of code
maxRetriesPerRequest: nulltypescript
// Bad -- missing maxRetriesPerRequest
const redis = new Redis("redis://localhost:6379");
const worker = new Worker("emails", processor, { connection: redis });
// BullMQ throws: "maxRetriesPerRequest must be null"Why bad: BullMQ requires infinite retries on Worker connections and will throw at startup without
nullSee examples/core.md for the full connection factory with producer vs consumer separation.
BullMQ v5要求每个构造函数都传入显式的Redis连接。Worker需要设置,使ioredis无限重试而非放弃。
maxRetriesPerRequest: nulltypescript
import Redis from "ioredis";
function createBullMQConnection(): Redis {
const url = process.env.REDIS_URL;
if (!url) throw new Error("REDIS_URL is required");
return new Redis(url, { maxRetriesPerRequest: null });
}
export { createBullMQConnection };优势: 满足BullMQ的要求,工厂确保配置一致,环境变量避免凭证硬编码
maxRetriesPerRequest: nulltypescript
// 错误示例 —— 缺少maxRetriesPerRequest
const redis = new Redis("redis://localhost:6379");
const worker = new Worker("emails", processor, { connection: redis });
// BullMQ抛出错误:"maxRetriesPerRequest must be null"问题: BullMQ要求Worker连接无限重试,启动时若未设置会抛出错误
null查看examples/core.md获取完整的连接工厂,包含生产者与消费者分离逻辑。
Pattern 2: Queue and Worker Setup
模式2:Queue与Worker设置
Queue adds jobs; Worker processes them. Both accept TypeScript generics for type-safe job data and return values.
typescript
import { Queue, Worker, type Job } from "bullmq";
interface EmailJobData {
to: string;
subject: string;
body: string;
}
const QUEUE_NAME = "emails";
const emailQueue = new Queue<EmailJobData>(QUEUE_NAME, {
connection: createBullMQConnection(),
});
const emailWorker = new Worker<EmailJobData>(
QUEUE_NAME,
async (job: Job<EmailJobData>) => {
await sendEmail(job.data.to, job.data.subject, job.data.body);
},
{ connection: createBullMQConnection() },
);Why good: Generics enforce type safety on , separate connections for Queue and Worker, named constant for queue name
job.dataSee examples/core.md for full setup with events, error handling, and typed return values.
Queue负责添加作业;Worker负责处理作业。两者都支持TypeScript泛型,实现类型安全的作业数据与返回值。
typescript
import { Queue, Worker, type Job } from "bullmq";
interface EmailJobData {
to: string;
subject: string;
body: string;
}
const QUEUE_NAME = "emails";
const emailQueue = new Queue<EmailJobData>(QUEUE_NAME, {
connection: createBullMQConnection(),
});
const emailWorker = new Worker<EmailJobData>(
QUEUE_NAME,
async (job: Job<EmailJobData>) => {
await sendEmail(job.data.to, job.data.subject, job.data.body);
},
{ connection: createBullMQConnection() },
);优势: 泛型强制的类型安全,Queue与Worker使用独立连接,队列名称使用命名常量
job.data查看examples/core.md获取完整设置,包含事件、错误处理和类型化返回值。
Pattern 3: Job Options
模式3:作业选项
Control job behavior with options: delay, priority, retries, backoff, and auto-removal.
typescript
const MAX_ATTEMPTS = 5;
const BACKOFF_DELAY_MS = 1000;
const DELAY_MS = 60_000;
const KEEP_COMPLETED_COUNT = 100;
await emailQueue.add(
"send-welcome",
{ to: "user@example.com", subject: "Welcome", body: "..." },
{
delay: DELAY_MS,
priority: 1, // 1 = highest, higher numbers = lower priority
attempts: MAX_ATTEMPTS,
backoff: { type: "exponential", delay: BACKOFF_DELAY_MS },
removeOnComplete: { count: KEEP_COMPLETED_COUNT },
removeOnFail: false,
},
);Why good: Named constants for all numeric values, exponential backoff for transient failures, with count prevents unbounded Redis memory, preserves failed jobs for debugging
removeOnCompleteremoveOnFail: falseSee examples/core.md for , , and LIFO patterns.
defaultJobOptionsaddBulk通过选项控制作业行为:延迟、优先级、重试、退避和自动移除。
typescript
const MAX_ATTEMPTS = 5;
const BACKOFF_DELAY_MS = 1000;
const DELAY_MS = 60_000;
const KEEP_COMPLETED_COUNT = 100;
await emailQueue.add(
"send-welcome",
{ to: "user@example.com", subject: "Welcome", body: "..." },
{
delay: DELAY_MS,
priority: 1, // 1 = 最高优先级,数值越高优先级越低
attempts: MAX_ATTEMPTS,
backoff: { type: "exponential", delay: BACKOFF_DELAY_MS },
removeOnComplete: { count: KEEP_COMPLETED_COUNT },
removeOnFail: false,
},
);优势: 所有数值使用命名常量,指数退避处理临时故障,设置数量防止Redis内存无限增长,保留失败作业用于调试
removeOnCompleteremoveOnFail: false查看examples/core.md获取、和LIFO模式。
defaultJobOptionsaddBulkPattern 4: Graceful Shutdown
模式4:优雅关闭
Call on process signals to finish in-progress jobs before exiting. Without this, jobs become stalled and are re-processed by other Workers.
worker.close()typescript
async function shutdown(workers: Worker[]): Promise<void> {
await Promise.all(workers.map((w) => w.close()));
process.exit(0);
}
process.on("SIGTERM", () => shutdown([emailWorker]));
process.on("SIGINT", () => shutdown([emailWorker]));Why good: stops accepting new jobs and waits for in-progress jobs to finish, handles multiple workers, signal handlers cover both SIGTERM and SIGINT
close()Promise.allGotcha: has no built-in timeout. If a processor hangs, the shutdown hangs. Wrap with your own timeout if needed.
worker.close()See examples/core.md for shutdown with timeout pattern.
在收到进程信号时调用,在退出前完成正在处理的作业。否则,作业会陷入停滞并被其他Worker重新处理。
worker.close()typescript
async function shutdown(workers: Worker[]): Promise<void> {
await Promise.all(workers.map((w) => w.close()));
process.exit(0);
}
process.on("SIGTERM", () => shutdown([emailWorker]));
process.on("SIGINT", () => shutdown([emailWorker]));优势: 停止接收新作业并等待正在处理的作业完成,处理多个Worker,信号处理器覆盖SIGTERM和SIGINT
close()Promise.all注意事项: 没有内置超时。如果处理器挂起,关闭操作也会挂起。必要时可自行添加超时包装。
worker.close()查看examples/core.md获取带超时的关闭模式。
Pattern 5: FlowProducer (Parent-Child Jobs)
模式5:FlowProducer(父子作业)
FlowProducer creates job trees where parent jobs wait for all children to complete. The entire tree is added atomically.
typescript
import { FlowProducer } from "bullmq";
const flowProducer = new FlowProducer({ connection: createBullMQConnection() });
await flowProducer.add({
name: "publish-video",
queueName: "videos",
children: [
{ name: "extract-audio", queueName: "media", data: { videoId: "abc" } },
{
name: "generate-thumbnail",
queueName: "media",
data: { videoId: "abc" },
},
{
name: "transcode",
queueName: "media",
data: { videoId: "abc", format: "mp4" },
},
],
});Why good: Parent waits until all children complete, atomic addition (all or nothing), children can be in different queues, parent processor can access children results via
job.getChildrenValues()See examples/advanced.md for accessing children values and nested flows.
FlowProducer创建作业树,父作业等待所有子作业完成。整个作业树会被原子性添加。
typescript
import { FlowProducer } from "bullmq";
const flowProducer = new FlowProducer({ connection: createBullMQConnection() });
await flowProducer.add({
name: "publish-video",
queueName: "videos",
children: [
{ name: "extract-audio", queueName: "media", data: { videoId: "abc" } },
{
name: "generate-thumbnail",
queueName: "media",
data: { videoId: "abc" },
},
{
name: "transcode",
queueName: "media",
data: { videoId: "abc", format: "mp4" },
},
],
});优势: 父作业等待所有子作业完成,原子性添加(要么全部成功,要么全部失败),子作业可位于不同队列,父处理器可通过访问子作业结果
job.getChildrenValues()查看examples/advanced.md获取访问子作业值和嵌套流的方法。
Pattern 6: Job Schedulers (Repeatable/Cron Jobs)
模式6:作业调度器(重复/定时作业)
Use (v5.16.0+) for repeatable jobs. Replaces the deprecated option.
upsertJobSchedulerrepeattypescript
const REPORT_INTERVAL_MS = 3_600_000; // 1 hour
// Fixed interval
await emailQueue.upsertJobScheduler(
"hourly-digest",
{ every: REPORT_INTERVAL_MS },
{
name: "send-digest",
data: { type: "hourly" },
},
);
// Cron expression -- daily at 3:15 AM
await emailQueue.upsertJobScheduler(
"nightly-cleanup",
{ pattern: "0 15 3 * * *" },
{
name: "cleanup",
data: { type: "nightly" },
opts: { removeOnComplete: true },
},
);Why good: is idempotent (safe to call on every startup), template defines job name/data/opts inherited by each produced job, cron syntax via
upsertJobSchedulerpatternGotcha: intervals align to the clock, not to when you called . A 2000ms interval fires at 0s, 2s, 4s, etc.
everyupsertJobSchedulerSee examples/advanced.md for removing schedulers and listing active schedulers.
使用(v5.16.0+)处理重复作业。替代已废弃的选项。
upsertJobSchedulerrepeattypescript
const REPORT_INTERVAL_MS = 3_600_000; // 1小时
// 固定间隔
await emailQueue.upsertJobScheduler(
"hourly-digest",
{ every: REPORT_INTERVAL_MS },
{
name: "send-digest",
data: { type: "hourly" },
},
);
// Cron表达式 —— 每天凌晨3:15执行
await emailQueue.upsertJobScheduler(
"nightly-cleanup",
{ pattern: "0 15 3 * * *" },
{
name: "cleanup",
data: { type: "nightly" },
opts: { removeOnComplete: true },
},
);优势: 是幂等的(每次启动调用都安全),模板定义每个生成作业继承的名称/数据/选项,通过支持cron语法
upsertJobSchedulerpattern注意事项: 间隔与时钟对齐,而非调用的时间。2000ms间隔会在0s、2s、4s等时间点触发。
everyupsertJobScheduler查看examples/advanced.md获取移除调度器和列出活动调度器的方法。
Pattern 7: Rate Limiting
模式7:速率限制
Limit how many jobs a Worker processes per time window. The limiter is global across all Workers on the same queue.
typescript
const RATE_LIMIT_MAX = 10;
const RATE_LIMIT_DURATION_MS = 1000;
const worker = new Worker("api-calls", processor, {
connection: createBullMQConnection(),
limiter: { max: RATE_LIMIT_MAX, duration: RATE_LIMIT_DURATION_MS },
});Why good: Global rate limit (10 workers with this config still process max 10 jobs/second total), named constants for limits
For dynamic rate limiting based on external API responses, use + . See examples/advanced.md.
worker.rateLimit(duration)throw Worker.RateLimitError()限制Worker在指定时间窗口内处理的作业数量。限制器对同一队列的所有Worker全局生效。
typescript
const RATE_LIMIT_MAX = 10;
const RATE_LIMIT_DURATION_MS = 1000;
const worker = new Worker("api-calls", processor, {
connection: createBullMQConnection(),
limiter: { max: RATE_LIMIT_MAX, duration: RATE_LIMIT_DURATION_MS },
});优势: 全局速率限制(10个Worker使用此配置仍总共最多每秒处理10个作业),限制值使用命名常量
如需基于外部API响应的动态速率限制,使用 + 。查看examples/advanced.md。
worker.rateLimit(duration)throw Worker.RateLimitError()Pattern 8: QueueEvents for Global Monitoring
模式8:QueueEvents全局监控
QueueEvents uses Redis Streams (not Pub/Sub) so events are reliable even across reconnections. Requires its own dedicated connection.
typescript
import { QueueEvents } from "bullmq";
const queueEvents = new QueueEvents(QUEUE_NAME, {
connection: createBullMQConnection(),
});
queueEvents.on("completed", ({ jobId, returnvalue }) => {
console.log(`Job ${jobId} completed with: ${returnvalue}`);
});
queueEvents.on("failed", ({ jobId, failedReason }) => {
console.error(`Job ${jobId} failed: ${failedReason}`);
});Why good: Monitors all Workers on a queue from a single listener, reliable delivery via Redis Streams, separate connection as required
See examples/advanced.md for progress tracking and waiting for specific job completion.
</patterns>
<performance>
QueueEvents使用Redis Streams(而非Pub/Sub),因此即使重新连接,事件也能可靠传递。需要专用的独立连接。
typescript
import { QueueEvents } from "bullmq";
const queueEvents = new QueueEvents(QUEUE_NAME, {
connection: createBullMQConnection(),
});
queueEvents.on("completed", ({ jobId, returnvalue }) => {
console.log(`作业 ${jobId} 完成,返回值:${returnvalue}`);
});
queueEvents.on("failed", ({ jobId, failedReason }) => {
console.error(`作业 ${jobId} 失败,原因:${failedReason}`);
});优势: 通过单个监听器监控队列上的所有Worker,通过Redis Streams实现可靠交付,按要求使用独立连接
查看examples/advanced.md获取进度跟踪和等待特定作业完成的方法。
</patterns>
<performance>
Performance Optimization
性能优化
- Concurrency -- Set on Worker options to process multiple jobs in parallel within a single Worker instance:
concurrency. Only effective for I/O-bound work. CPU-bound work blocks the event loop and causes stalled jobs -- use sandboxed processors instead.{ concurrency: 10 } - Sandboxed processors -- Pass a file path instead of a function to the Worker constructor to run processors in a separate process (or worker thread with ). Prevents CPU-intensive work from blocking lock renewal.
useWorkerThreads: true - addBulk -- Use to add many jobs in a single Redis round-trip instead of calling
queue.addBulk([...])in a loop.queue.add() - removeOnComplete/Fail -- Configure auto-removal to prevent unbounded Redis memory growth. Use to keep the last N jobs for debugging.
{ count: N } - Separate connections -- Producers (Queue) can share a connection. Workers and QueueEvents each need their own connection due to blocking Redis commands.
<decision_framework>
- 并发 —— 在Worker选项中设置,使单个Worker实例并行处理多个作业:
concurrency。仅对I/O密集型任务有效。CPU密集型任务会阻塞事件循环并导致作业停滞——请改用沙箱处理器。{ concurrency: 10 } - 沙箱处理器 —— 向Worker构造函数传入文件路径而非函数,使处理器在独立进程中运行(或使用在工作线程中运行)。防止CPU密集型任务阻塞锁续期。
useWorkerThreads: true - addBulk —— 使用在单个Redis往返中添加多个作业,而非循环调用
queue.addBulk([...])。queue.add() - removeOnComplete/Fail —— 配置自动移除,防止Redis内存无限增长。使用保留最后N个作业用于调试。
{ count: N } - 独立连接 —— 生产者(Queue)可共享连接。Worker和QueueEvents各自需要独立连接,因为它们使用阻塞Redis命令。
<decision_framework>
Decision Framework
决策框架
When to Use BullMQ
是否使用BullMQ
Do you need background job processing?
|-- NO -> Don't use BullMQ
+-- YES -> Do you need persistence, retries, or scheduling?
|-- NO -> Simple in-process queue or setTimeout may suffice
+-- YES -> Do you need parent-child job dependencies?
|-- YES -> BullMQ with FlowProducer
+-- NO -> Do you need rate limiting or priority?
|-- YES -> BullMQ with limiter/priority options
+-- NO -> BullMQ with basic Queue + Worker是否需要后台作业处理?
|-- 否 -> 不要使用BullMQ
+-- 是 -> 是否需要持久化、重试或调度?
|-- 否 -> 简单的进程内队列或setTimeout可能足够
+-- 是 -> 是否需要父子作业依赖?
|-- 是 -> 使用带FlowProducer的BullMQ
+-- 否 -> 是否需要速率限制或优先级?
|-- 是 -> 使用带限制器/优先级选项的BullMQ
+-- 否 -> 使用基础的Queue + Worker版BullMQWhich Job Pattern?
选择哪种作业模式?
What kind of job scheduling do you need?
|-- One-time delayed job -> queue.add() with delay option
|-- Recurring on fixed interval -> upsertJobScheduler with every
|-- Recurring on cron schedule -> upsertJobScheduler with pattern
|-- Job that depends on other jobs -> FlowProducer with children
|-- Bulk of independent jobs -> queue.addBulk([...])需要哪种作业调度?
|-- 一次性延迟作业 -> 使用带delay选项的queue.add()
|-- 固定间隔重复作业 -> 使用带every的upsertJobScheduler
|-- Cron调度重复作业 -> 使用带pattern的upsertJobScheduler
|-- 依赖其他作业的作业 -> 使用带children的FlowProducer
|-- 批量独立作业 -> 使用queue.addBulk([...])Concurrency Strategy
并发策略
Is the processor CPU-intensive?
|-- YES -> Use sandboxed processor (file path or useWorkerThreads)
+-- NO -> Is it I/O-bound (network calls, DB queries)?
|-- YES -> Set concurrency option (e.g., 10-50)
+-- NO -> Default concurrency (1) is fine</decision_framework>
<red_flags>
处理器是否为CPU密集型?
|-- 是 -> 使用沙箱处理器(文件路径或useWorkerThreads)
+-- 否 -> 是否为I/O密集型(网络调用、数据库查询)?
|-- 是 -> 设置concurrency选项(如10-50)
+-- 否 -> 默认并发数(1)即可</decision_framework>
<red_flags>
RED FLAGS
风险警示
High Priority Issues:
- Missing on Queue/Worker/QueueEvents constructor -- BullMQ v5 throws at startup without it
connection - Missing on Worker connections -- BullMQ throws immediately
maxRetriesPerRequest: null - No graceful shutdown handler -- in-progress jobs become stalled on process exit and are re-processed by other Workers
- Using deprecated option on
repeatinstead ofqueue.add()-- deprecated since v5.16.0upsertJobScheduler - Using integer job IDs -- BullMQ v5 throws; IDs must be strings
Medium Priority Issues:
- No /
removeOnCompleteconfigured -- completed/failed jobs accumulate in Redis indefinitelyremoveOnFail - Sharing a single ioredis connection between Worker and QueueEvents -- both use blocking commands and will interfere
- CPU-intensive processor without sandboxed mode -- blocks event loop, causes stalled jobs, prevents lock renewal
- Missing error event handler on Worker -- prevents unhandled errors from crashing the process
worker.on("error", ...)
Common Mistakes:
- Assuming has a timeout -- it waits indefinitely for processors to finish; wrap with your own timeout
worker.close() - Using QueueScheduler class -- removed in BullMQ v4; its responsibilities are handled by Workers automatically
- Expecting to be per-Worker -- the rate limit is global across all Workers on the same queue
limiter - Not making processors idempotent -- BullMQ guarantees at-least-once delivery; a stalled job may be processed twice
Gotchas & Edge Cases:
upsertJobSchedulerintervals align to the clock (0s, 2s, 4s), not to when you called the methodevery- does not cancel running processors -- it waits for them to finish naturally
worker.close() - Job priority has a performance cost -- BullMQ uses a different data structure for priority queues; skip if not needed
- uses Redis Streams internally -- ensure your Redis instance has sufficient memory for stream data
QueueEvents - FlowProducer adds the entire tree atomically -- if any child fails validation, none are added
- returns an object keyed by
job.getChildrenValues()-- not an array"queueName:jobId" - Redis must have set to
maxmemory-policy-- BullMQ relies on keys not being evictednoeviction
</red_flags>
<critical_reminders>
高优先级问题:
- Queue/Worker/QueueEvents构造函数缺少——BullMQ v5启动时会抛出错误
connection - Worker连接缺少——BullMQ会立即抛出错误
maxRetriesPerRequest: null - 无优雅关闭处理器——进程退出时正在处理的作业会停滞,并被其他Worker重新处理
- 使用已废弃的中的
queue.add()选项而非repeat——v5.16.0版本起已废弃upsertJobScheduler - 使用整数作业ID——BullMQ v5会抛出错误;ID必须为字符串
中优先级问题:
- 未配置/
removeOnComplete——完成/失败的作业会在Redis中无限累积removeOnFail - Worker和QueueEvents共享单个ioredis连接——两者都使用阻塞命令,会互相干扰
- CPU密集型处理器未使用沙箱模式——阻塞事件循环,导致作业停滞,阻止锁续期
- Worker缺少错误事件处理器——防止未处理错误导致进程崩溃
worker.on("error", ...)
常见错误:
- 假设有超时——它会无限等待处理器完成;必要时自行添加超时包装
worker.close() - 使用QueueScheduler类——在BullMQ v4中已移除;其职责现在由Worker自动处理
- 认为是单Worker限制——速率限制对同一队列的所有Worker全局生效
limiter - 处理器未实现幂等性——BullMQ保证至少一次交付;停滞的作业可能被处理两次
注意事项与边缘情况:
- 的
upsertJobScheduler间隔与时钟对齐(0s、2s、4s),而非调用方法的时间every - 不会取消运行中的处理器——它会等待处理器自然完成
worker.close() - 作业优先级会产生性能开销——BullMQ对优先级队列使用不同的数据结构;非必要时不要使用
- 内部使用Redis Streams——确保Redis实例有足够内存存储流数据
QueueEvents - FlowProducer原子性添加整个作业树——如果任何子作业验证失败,所有作业都不会被添加
- 返回以
job.getChildrenValues()为键的对象——而非数组"queueName:jobId" - Redis必须将设置为
maxmemory-policy——BullMQ依赖于键不被驱逐noeviction
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
重要提醒
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,, named constants)import type
(You MUST pass a object to every Queue, Worker, QueueEvents, and FlowProducer constructor -- BullMQ v5 throws if connection is missing)
connection(You MUST set on ioredis connections used by Workers -- BullMQ requires infinite retries and throws without this setting)
maxRetriesPerRequest: null(You MUST call on SIGTERM/SIGINT for graceful shutdown -- without it, in-progress jobs become stalled)
await worker.close()(You MUST use for repeatable/cron jobs -- the old option on is deprecated since v5.16.0)
upsertJobSchedulerrepeatqueue.addFailure to follow these rules will cause startup crashes, stalled jobs, and unreliable job processing.
</critical_reminders>
所有代码必须遵循CLAUDE.md中的项目规范(短横线命名、命名导出、导入顺序、、命名常量)import type
(必须为每个Queue、Worker、QueueEvents和FlowProducer构造函数传入对象——如果缺少连接,BullMQ v5会抛出错误)
connection(必须为Worker使用的ioredis连接设置——BullMQ要求无限重试,缺少该设置会抛出错误)
maxRetriesPerRequest: null(必须在SIGTERM/SIGINT信号触发时调用以优雅关闭——否则,正在处理的作业会陷入停滞状态)
await worker.close()(必须使用处理重复/定时作业——v5.16.0版本起,中的旧选项已废弃)
upsertJobSchedulerqueue.addrepeat不遵循这些规则会导致启动崩溃、作业停滞和不可靠的作业处理。
</critical_reminders>