Back to Blog

BullMQ vs Agenda: Node.js Job Queues for SaaS

Published: June 29, 2026
BullMQ vs Agenda: Node.js Job Queues for SaaS

Every SaaS hits the same wall: a route handler that does too much. Generating a PDF export, syncing a CRM, fanning out webhooks — do that work inside the open HTTP request and you block the event loop, drop connections, and watch your p99 latency climb. The fix is a background job queue, and in Node.js the BullMQ vs Agenda question is the one teams actually argue about.

Short answer: BullMQ for almost everyone, Agenda if you're MongoDB-only and schedule-heavy, Bee-Queue if you have one dead-simple high-volume task. The real divide isn't a benchmark — it's the data store. BullMQ keeps queue state in Redis and moves jobs with atomic operations; Agenda persists jobs as MongoDB documents and polls for them. That single architectural difference decides how each behaves when your traffic gets serious.

BullMQ vs Agenda — a job queue is a conveyor that moves work off your request thread

BullMQ: The Redis-Backed Default

BullMQ is the maintained, TypeScript-first successor to Bull, and it's our default for NestJS SaaS work. It leans on Redis primitives and atomic Lua scripts to manage queue state:

TEXT
1[NestJS app] ──enqueue──> [atomic Lua script] ──> [Redis sorted set / stream] ──> [worker process]

Because Redis runs commands on a single-threaded loop, BullMQ can bundle a multi-step operation — move a job from delayed to active and apply a rate limit and record the attempt — into one atomic script that executes in memory. That's what kills the race conditions you'd otherwise hand-roll, and it's why BullMQ handles high-velocity workloads (webhook fan-out, bulk exports, notification storms) without leaning on your primary database at all. If you're standing up the broader system, our background job queue architecture for NestJS covers the worker topology around it.

Agenda: MongoDB-Native, Schedule-First

If your stack is built entirely on MongoDB, Agenda is a reasonable, database-native choice that saves you from running a separate Redis instance. It serializes each job as a document in a Mongo collection, which makes it genuinely pleasant for long-range, calendar-driven jobs — "run this on the 1st of next month" persists naturally on disk.

The trade-off is the mechanism: Agenda polls the database with findAndModify to claim the next job. At low volume that's invisible. As volume climbs, that constant polling turns into disk I/O and row-lock contention on the same database serving your app — your queue and your product start fighting over the same connection pool. Agenda is a scheduler that happens to be a queue, and it's best kept to the workloads where that's true.

Atomic Redis operations move jobs in memory, the core BullMQ vs Agenda difference

Bee-Queue: The Minimalist

Bee-Queue is also Redis-backed, but it strips out everything that isn't raw speed. No cron, no parent-child dependencies, no global rate limiting — just a fast, tiny queue for one simple job type done at volume, like resizing uploaded images. It's excellent at exactly that and the wrong tool the moment you need scheduling or job chains. Most teams who reach for Bee-Queue eventually migrate to BullMQ when feature needs grow; if you already know you'll need those features, start on BullMQ.

BullMQ vs Agenda vs Bee-Queue: The Feature Grid

CapabilityBullMQAgendaBee-Queue
Data storeRedis (streams/hashes)MongoDB collectionRedis (lists)
Job claimingAtomic Lua, in-memoryDB interval pollingBasic Redis ops
Relative throughputHighLow–moderate (polling-bound)High (single-purpose)
Parent-child jobsYesNoNo
Global rate limitingYesConcurrency caps onlyNo
Long-range schedulingGood (Redis RAM)Excellent (disk-persistent)Limited

The throughput column is relative on purpose — I'm not going to quote you a "10,000 jobs/sec" number from a benchmark that used a machine and payload nothing like yours. The architectural truth is what carries over: in-memory atomic ops scale further than database polling, full stop.

Production: BullMQ in NestJS

Background workers processing jobs off the request thread in a BullMQ vs Agenda setup

BullMQ is our recommendation for multi-tenant products, and the official @nestjs/bullmq package keeps the wiring clean. A processor with concurrency and rate limiting:

TypeScript
1// src/processors/crm-sync.processor.ts
2import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq';
3import { Job } from 'bullmq';
4import { Logger } from '@nestjs/common';
5
6@Processor('crm-sync-queue', {
7  concurrency: 5,                            // 5 jobs in parallel per worker
8  limiter: { max: 100, duration: 10_000 },   // at most 100 jobs / 10s
9})
10export class CrmSyncProcessor extends WorkerHost {
11  private readonly logger = new Logger(CrmSyncProcessor.name);
12
13  async process(job: Job<{ tenantId: string; payload: unknown }>): Promise<{ ok: true }> {
14    const { tenantId } = job.data;
15    this.logger.log(`Syncing CRM for tenant ${tenantId}`);
16    // Any thrown error here triggers BullMQ's retry/backoff automatically.
17    return { ok: true };
18  }
19
20  @OnWorkerEvent('failed')
21  onFailed(job: Job, err: Error) {
22    this.logger.error(`Job ${job.id} failed: ${err.message}`);
23  }
24}

The detail people skip is the retry policy. Configure exponential backoff with jitter on the queue, because the third-party API that "never goes down" is the one that hangs on a Tuesday — and a queue without backoff answers an outage by stampeding the recovering service. This matters most for outgoing webhook delivery, where the receiver is someone else's flaky endpoint.

Which One to Pick

  • BullMQ — scaling multi-tenant SaaS, high-velocity processing, anything needing retries, rate limits, or job chains. The default. (You'll likely already have Redis in your stack for caching, so the marginal infra cost is near zero.)
  • Agenda — MongoDB-only infrastructure, sub-few-hundred-jobs-per-second volume, and a feature set dominated by future-dated scheduling.
  • Bee-Queue — one simple, high-volume task and a hard requirement to keep the footprint minimal.

Pick the queue that matches your data store and your failure modes, not the one with the loudest throughput claim. A queue's real job is to keep doing work quietly while a third-party API is having its worst day — and the one you'll thank yourself for at 3am is the one whose retries you actually configured.

Frequently Asked Questions

For most SaaS workloads, BullMQ. It's Redis-backed and uses atomic Lua scripts to move jobs between states in memory, which avoids the database polling that makes Agenda struggle under load. Choose Agenda only if your stack is already MongoDB-only and your jobs are low-volume, calendar-driven schedules where avoiding a separate Redis instance is worth the throughput trade-off.

Queues like Agenda poll the database (findAndModify) to find and lock the next job. As volume grows, that constant polling generates disk I/O and row-lock contention on your primary database — the same database serving your app. Redis-backed queues like BullMQ hold and mutate queue state in memory with atomic operations, so they sidestep disk I/O and don't compete with your app's database connections.

Use Bee-Queue when you have one simple, high-volume job type — image resizing, thumbnail generation — and you want minimal overhead. It's Redis-backed and fast but deliberately drops cron scheduling, parent-child job dependencies, and advanced rate limiting. The moment you need any of those, you've outgrown Bee-Queue and should be on BullMQ.

A backoff strategy controls how long a worker waits before retrying a failed job. Without it, a third-party API outage turns your queue into a retry storm that hammers the recovering service and your own workers. Exponential backoff with jitter spaces retries out and adds randomness so a thousand jobs don't all retry on the same tick — giving the downstream system room to recover.

Avoid Kue (deprecated, known memory issues) and the original Bull package — it's superseded by BullMQ, which is the maintained, TypeScript-first successor from the same authors. If you're on legacy Bull or Kue, treat migrating to BullMQ as the default, not a research project.

Portrait of Umar Farooq

About Umar Farooq

Umar Farooq is the founder and lead engineer of Codify SaaS. He builds B2B SaaS products and web applications on modern TypeScript stacks and enterprise Java, and writes code-first guides drawn from real production work — the schema decisions, the migrations that almost went wrong, and the performance fixes that actually moved the numbers. When he recommends an approach, he shows the code and explains the trade-offs.

Read full bio