BullMQ vs BeeQueue: A Complete Comparison Guide
Every Node.js application eventually needs background job processing — sending emails, processing images, generating reports, handling webhooks. The choice of queue library shapes your architecture for years.
Two libraries dominate the conversation: BullMQ and BeeQueue. Both are Redis-backed job queues for Node.js, but their philosophies diverge sharply. BullMQ is a full-featured, battle-tested successor to Bull with 5.9M+ weekly npm downloads and 9.1k GitHub stars. BeeQueue is a lightweight contender focused on speed and simplicity, with ~100K weekly npm downloads and 4k GitHub stars.
This guide provides an honest, detailed comparison to help you choose based on your actual requirements — not hype.
Side-by-Side at a Glance
| Feature | BullMQ (v5.x) | BeeQueue (v2.x) |
|---|---|---|
| Backend | Redis / Valkey / DragonflyDB | Redis |
| Codebase size | ~50K LOC | ~1K LOC |
| Weekly npm downloads | 5.9M+ | ~100K |
| TypeScript | First-class | Community types |
| Multi-language | Node, Python, Elixir, Rust, PHP | Node.js only |
| Delayed jobs | ✅ Millisecond precision | ✅ With limitations |
| Repeatable / Cron | ✅ Full cron + interval | ❌ Not supported |
| Rate limiting | ✅ Per-queue | ❌ Not supported |
| Job flows (DAGs) | ✅ FlowProducer | ❌ Not supported |
| Groups | ✅ Pro feature | ❌ Not supported |
| Retry + backoff | ✅ Built-in (fixed, exponential, custom) | ✅ Built-in (fixed, exponential) |
| Sandboxed workers | ✅ Child-process workers | ❌ Not supported |
| Stalled job handling | ✅ Automatic recovery | ✅ Periodic check-in |
| Queue events | ✅ Global event stream | ✅ Pub/sub events |
| Pause / Resume | ✅ | ❌ Not supported |
| UI dashboard | Queue Hub / Arena | Arena (community) |
Feature-by-Feature Deep Dive
Job Reliability & Stalled Jobs
Both libraries provide at-least-once delivery, but their stalled-job recovery mechanisms differ.
BullMQ uses Lua scripts for atomic state transitions and automatically detects stalled jobs after a configurable stallInterval (default 30s). When a worker crashes mid-job, the job is automatically re-enqueued:
import { Worker } from 'bullmq';
const worker = new Worker('paint', async job => {
await paintCar(job.data);
}, {
connection: { host: 'localhost', port: 6379 },
stalledInterval: 30000,
maxStalledCount: 1,
});
BeeQueue workers periodically "check in" within stallInterval (default 5s). When a worker stops checking in, checkStalledJobs() re-enqueues the active job:
const Queue = require('bee-queue');
const queue = new Queue('paint', {
stallInterval: 5000,
redis: { host: 'localhost', port: 6379 },
});
Verdict: Both provide reliable at-least-once delivery. BullMQ's mechanism is more mature with configurable visibility windows. BeeQueue's shorter default stall interval means faster detection of crashed workers.
Job Retries & Backoff Strategies
BullMQ offers configurable retry attempts with built-in fixed and exponential backoff, plus support for custom backoff functions and dead-letter patterns:
import { Queue } from 'bullmq';
const emailQueue = new Queue('email', { connection });
await emailQueue.add('welcome', { to: 'user@example.com' }, {
attempts: 5,
backoff: {
type: 'exponential',
delay: 2000, // 2s, 4s, 8s, 16s, 32s
},
});
// Custom backoff function:
await emailQueue.add('critical', { data }, {
attempts: 3,
backoff: {
type: 'custom',
delay: (attemptsMade, err) => {
return Math.min(attemptsMade * 5000, 30000);
},
},
});
BeeQueue provides retries via a chainable API with fixed and exponential strategies:
const Queue = require('bee-queue');
const queue = new Queue('email');
const job = queue
.createJob({ to: 'user@example.com' })
.retries(5)
.backoff('exponential', 2000)
.save();
Verdict: Both handle retries well. BullMQ gives you more control (custom backoff functions, dead-letter queues). BeeQueue's chainable API is simpler but less flexible.
Scheduling & Repeatable Jobs
This is the single biggest differentiator between the two libraries.
BullMQ supports repeatable jobs via cron expressions and fixed intervals, with timezone-aware scheduling and deduplication:
import { Queue } from 'bullmq';
const reportQueue = new Queue('reports');
// Every day at 9 AM Eastern:
await reportQueue.add('daily-summary', { type: 'sales' }, {
repeat: {
pattern: '0 9 * * *',
tz: 'America/New_York',
},
});
// Every 30 minutes:
await reportQueue.add('sync', { source: 'stripe' }, {
repeat: {
every: 30 * 60 * 1000,
},
});
BeeQueue has no repeatable jobs or cron support. You can fake it by re-enqueuing inside the processor, but there's no built-in schedule management, cron parsing, or timezone support:
// BeeQueue — manual "repeatable" workaround
queue.process(async (job) => {
await processJob(job.data);
// Manually re-enqueue
if (job.data.repeat) {
queue
.createJob(job.data)
.delayUntil(Date.now() + job.data.interval)
.save();
}
});
Verdict: BullMQ wins decisively for scheduled/cron workloads. If you need recurring jobs, choose BullMQ.
Rate Limiting
BullMQ has built-in rate limiting — max jobs per time window, configurable per queue and per worker:
import { Worker } from 'bullmq';
const worker = new Worker('api-calls', async job => {
await callExternalApi(job.data);
}, {
limiter: {
max: 100,
duration: 1000, // 100 jobs per second
},
connection,
});
BeeQueue has no rate limiting — you must implement it externally.
Verdict: BullMQ handles rate limiting natively. Critical for API integrations, webhook delivery, and third-party rate limits.
Priority Queues
BullMQ offers true priority support via Redis sorted sets with arbitrary numeric values:
import { Queue } from 'bullmq';
const queue = new Queue('transactions');
await queue.add('charge', { amount: 100 }, { priority: 1 }); // highest
await queue.add('refund', { amount: 50 }, { priority: 5 }); // medium
await queue.add('report', { data: {} }, { priority: 10 }); // lowest
BeeQueue processes jobs FIFO by default with no native priority parameter. The workaround is using separate queues:
// BeeQueue — separate queues for priority
const highPriority = new Queue('emails-high');
const lowPriority = new Queue('emails-low');
Verdict: BullMQ handles priority natively and elegantly. BeeQueue requires multiple queues.
Job Flows / DAGs (FlowProducer)
One of BullMQ's standout features is FlowProducer, which creates parent-child job trees where children run in parallel and parents wait for all children:
import { FlowProducer } from 'bullmq';
const flow = new FlowProducer({ connection });
const tree = await flow.add({
name: 'video-pipeline',
queueName: 'pipeline',
data: { videoId: 123 },
children: [
{
name: 'transcode',
data: { format: 'hls' },
queueName: 'tasks',
children: [
{
name: 'thumbnail',
data: { dimensions: '1280x720' },
queueName: 'tasks',
},
],
},
{
name: 'analyze-audio',
data: {},
queueName: 'tasks',
},
],
});
This powers video processing pipelines, ETL workflows, and multi-step order fulfillment. BeeQueue has no equivalent — each job is completely independent.
Sandboxed Workers
BullMQ supports running job processors in isolated child processes via the sandbox option, preventing crashes and memory leaks from affecting the main process:
// worker.ts
import { Worker } from 'bullmq';
const worker = new Worker('image-processing', './processor.js');
// processor.js — runs in a child process
module.exports = async (job) => {
await processImage(job.data.path);
};
BeeQueue has no sandboxed workers — all processors run in the same process.
Performance & Scaling Comparison
Throughput Benchmarks
| Metric | BullMQ | BeeQueue |
|---|---|---|
| Max throughput (single Redis) | ~25,000 jobs/sec | ~15,000–40,000 jobs/s |
| Max with DragonflyDB | >250,000 jobs/sec | N/A |
| Latency (p99, simple job) | ~2–5ms | ~1–3ms |
| Redis commands per job | ~5–8 (Lua-optimized) | ~3–5 (pipelined) |
| Memory overhead per job | Higher (rich metadata) | Lower (minimal) |
Key insight: BeeQueue's smaller codebase means fewer Redis roundtrips for simple jobs. But at scale (>100 jobs/sec), BullMQ's Lua scripting optimizations close the gap. With DragonflyDB or Valkey, BullMQ can saturate much higher throughput.
Concurrency Model
Both support configurable concurrency. The difference is granularity:
// BullMQ — per-worker concurrency
new Worker('queue', processor, { concurrency: 50 });
// BeeQueue — per-queue concurrency
queue.process(50, async (job) => { ... });
BullMQ's per-worker concurrency lets multiple workers each have independent settings, while BeeQueue applies concurrency at the queue level.
Horizontal Scaling
BullMQ makes horizontal scaling first-class — add workers on any number of machines, auto-discover jobs via Redis pub/sub, and benefit from a globally-coordinated rate limiter. FlowProducer enables distributed DAG execution across a cluster.
BeeQueue supports multiple workers on the same queue via a brpoplpush-based design, but has no global rate limiting or distributed workflow coordination.
When to Choose BullMQ
Choose BullMQ when you need:
- Complex workflow orchestration — DAG job flows for video transcoding, ETL pipelines, multi-step order processing
- Scheduled/recurring tasks — Cron jobs, periodic syncs, daily reports
- Rate-limited API integrations — Webhook delivery, Stripe/Shopify API calls with strict rate limits
- Priority-based processing — Urgent jobs (payment processing, password resets) jump the queue
- Multi-tenant workloads — Groups (Pro) isolate noisy tenants
- Production reliability at scale — Thousands of jobs/sec with robust stalled-job recovery
- Multi-language teams — Same queues from Node.js, Python, Elixir, Rust, PHP
- TypeScript-first development — Full type definitions
Example architectures: Video processing SaaS, e-commerce order fulfillment, multi-tenant notification systems, data pipelines with scheduled ETL cron jobs.
When to Choose BeeQueue
Choose BeeQueue when you:
- Need simplicity — ~1K LOC, up and running in 5 minutes
- Value minimal dependencies — Microservices where every dependency matters
- Prioritize raw throughput for simple jobs — Fire-and-forget tasks with minimal overhead
- Don't need scheduling, rate limits, or priorities — Straightforward job processing
- Want smaller memory footprint — Less metadata stored in Redis per job
- Run on low-resource environments — Raspberry Pi, edge devices, Lambda containers
Example architectures: Simple email/SMS notification service, webhook delivery with basic retry, single-step image thumbnail generation, prototype/MVP.
Migration: BeeQueue to BullMQ
If you're outgrowing BeeQueue, migration to BullMQ is straightforward but requires attention to a few key differences:
| Concern | BeeQueue | BullMQ |
|---|---|---|
| Queue creation | new Queue(name) |
new Queue(name, { connection }) |
| Job creation | queue.createJob(data).save() |
queue.add(name, data, opts) |
| Processing | queue.process(fn) |
new Worker(name, fn, opts) |
| Retries | .retries(n).backoff(type, d) |
{ attempts: n, backoff: { type, delay } } |
| Events | queue.on('succeeded', cb) |
new QueueEvents(name).on('completed') |
| Progress | job.reportProgress(n) |
job.updateProgress(n) |
| Delayed jobs | .delayUntil(timestamp) |
{ delay: ms } |
| Concurrency | queue.process(n, fn) |
new Worker(name, fn, { concurrency: n }) |
Migration Strategy
Phase 1: Set up BullMQ with the same Redis instance alongside BeeQueue. Create jobs in both libraries during a transition window. Monitor both via Queue Hub.
Phase 2: Stop producing new BeeQueue jobs. Let existing jobs drain naturally. Verify all critical jobs migrate successfully.
Phase 3: Remove the BeeQueue dependency and deploy BullMQ-only code.
Common Pitfall: Retry Count Semantics
// BeeQueue — .retries(3) = 1 initial + 3 retries = 4 total
queue.createJob(data).retries(3).save();
// BullMQ — attempts: 3 = 3 total attempts
// When migrating: set attempts: 4 to match BeeQueue semantics
await queue.add('name', data, { attempts: 4 });
Other watchpoints: BullMQ requires a job name (first param to add()), explicit connection config, and uses a separate QueueEvents class for event listeners.
Monitor Both with QueueHub
Whether you choose BullMQ, BeeQueue, or run both during migration, you need visibility into your queues. QueueHub (formerly Queue Hub) is a SaaS dashboard that provides unified monitoring for both libraries:
- Live dashboards — throughput charts, processing time, worker status
- Job management — retry, promote, remove, and inspect jobs in detail
- Queue operations — pause, resume, clean, or delete queues
- Multi-backend support — Connect to BullMQ and BeeQueue queues from the same Redis instance
- Agent tunneling — Securely connect to Redis behind VPCs and firewalls
- Team collaboration — Multi-org support with invitations
// QueueHub config — connect to both libraries from one dashboard
{
connections: [
{
name: "Production",
backends: [
{
type: "bullmq",
queuePrefix: "bull",
host: "redis-prod.example.com",
port: 6379,
},
{
type: "beequeue",
queuePrefix: "bq",
host: "redis-prod.example.com",
port: 6379,
},
],
},
],
}
Single pane of glass for teams migrating from BeeQueue to BullMQ. Zero-code monitoring — no instrumentation required.
Decision Framework
Need scheduled/cron jobs?
├── Yes → BullMQ
└── No → Continue
Need rate limiting?
├── Yes → BullMQ
└── No → Continue
Need job flows / DAGs?
├── Yes → BullMQ
└── No → Continue
Need priority queuing?
├── Yes → BullMQ
└── No → Continue
Is simplicity your top priority?
├── Yes → BeeQueue
└── No → BullMQ (for future-proofing)
Quick Reference
| Your Situation | Recommendation |
|---|---|
| Building an MVP / prototype | BeeQueue |
| Simple notification queue | BeeQueue |
| Video / image processing pipeline | BullMQ |
| Multi-tenant SaaS platform | BullMQ (or Pro) |
| ETL / data sync with cron schedules | BullMQ |
| Webhook delivery with rate limits | BullMQ |
| Low-resource / edge deployment | BeeQueue |
| Enterprise production | BullMQ |
Key Takeaways
-
BullMQ is the right choice for production-grade, feature-rich queueing — scheduling, rate limits, priorities, job flows, and multi-language support. It's the industry standard for a reason.
-
BeeQueue excels where simplicity and minimal overhead matter — quick projects, simple job processing, and environments where ~1K LOC is preferable to ~50K LOC.
-
Performance is not the differentiator — both are fast enough for most use cases. The real difference is in features.
-
Migration from BeeQueue to BullMQ is straightforward but requires attention to retry-count semantics, connection handling, and event listener patterns.
-
QueueHub provides unified monitoring for both libraries, making it the ideal dashboard during migration or in mixed environments.
-
Your choice should be driven by feature requirements, not hype. Start with BeeQueue if your needs are simple. Start with BullMQ if you know you'll grow into advanced features.
Try QueueHub Today
Ready to take control of your BullMQ and BeeQueue queues? QueueHub gives you real-time visibility, job management, and team collaboration — all from a single dashboard.
Visit queuehub.tech to start your free trial. No credit card required. Connect your Redis instance and see your queues in minutes.
Related Articles
Best BullMQ Dashboard Alternatives in 2026: A Comprehensive Comparison
Comparing every BullMQ UI option side by side: Bull Board, Arena, Taskforce, QueueHub, and raw redis-cli. Feature matrices, pricing, pros and cons, and recommendations for every team size.
Testing and Development Workflows for BullMQ vs BeeQueue
A practical guide to testing job queue code with BullMQ and BeeQueue — test fixtures, producer and worker testing patterns, local development workflows, and CI integration.
BeeQueue Production Patterns and Redis Internals: Beyond the Basics
Go beyond basic BeeQueue setup. Master Redis key internals, Prometheus health checks, dead letter queues, graceful shutdown, batch processing, and benchmarking methodology.