NestJS BullMQ Implementation
Priority: P0 (Critical)
Guidelines
- Set idle polling: Add
drainDelay+stalledInterval+maxStalledCountto every@Processor. DefaultdrainDelay(5 ms) burns 570M Redis commands/day at idle. See patterns.md. - Throttle worker error logs: BullMQ workers emit raw unhandled ReplyErrors on Redis failure (e.g. Upstash rate limits). Always extend
BaseProcessorinstead ofWorkerHostto rate-limit these logs. See patterns.md. - Set job retention: Add
removeOnComplete,removeOnFail,attempts,backoffto everyBullModule.registerQueue. See patterns.md. - Use shared constants: All numeric options live in
src/common/constants/bull-queue.constants.ts. Key constants:QUEUE_DRAIN_DELAY_MS(10 000 ms),QUEUE_STALLED_INTERVAL_MS(60 000 ms). UsegetSharedBullQueueOptionshelper forregisterQueue. Queue/job names go in{feature}.constants.ts. Never inline magic numbers. - Wrap every
queue.add(): Persist DB record first, then enqueue inside try-catch. Redis errors must not surface as 500s. See patterns.md. - Throttler fail-open:
ThrottlerGuardis registered as globalAPP_GUARD— a Redis blip propagates errors to ALL HTTP routes.RedisThrottlerStorage.increment()must catch all Redis errors and return a fail-open pass-through record. A Redis blip must not kill all HTTP routes. See patterns.md. - Guard new queues: Follow
isRedisEnabled()conditional + mock token pattern in every module. NestJS DI throws on startup without mock. - Keep processor and cron: Cron schedules; processor executes. Both always required — they are complementary. See patterns.md.
- Use local Redis in dev: Never point dev machines at Upstash — idle workers exhaust free tier (500K/day) in minutes.
Anti-Patterns
- No bare
@Processor(NAME): Always pass worker options object withdrainDelayandstalledInterval. - No bare
WorkerHostextension: Always extendBaseProcessorinstead to intercept and rate-limit worker errors. - No
registerQueuewithoutdefaultJobOptions: Omitting causes unbounded Redis memory growth. - No inline numbers: Use
bull-queue.constants.ts— never write10_000,60_000,50,20,3, or5_000directly. - No unguarded
queue.add(): Wrap in try-catch; persist DB state first. - No throws in throttler increment: Catch Redis errors; return fail-open record.
- No missing mock token: Provide
getQueueTokenmock whenredisEnabled = false. - No removing processor because cron exists: They serve different roles.
- No cloud Redis in dev: Use local Docker Redis.