·QueueHub Team·12 min read

BullMQ vs BeeQueue: A Complete Comparison Guide

bullmqbeequeuecomparisonqueue-managementredis

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:

  1. Complex workflow orchestration — DAG job flows for video transcoding, ETL pipelines, multi-step order processing
  2. Scheduled/recurring tasks — Cron jobs, periodic syncs, daily reports
  3. Rate-limited API integrations — Webhook delivery, Stripe/Shopify API calls with strict rate limits
  4. Priority-based processing — Urgent jobs (payment processing, password resets) jump the queue
  5. Multi-tenant workloads — Groups (Pro) isolate noisy tenants
  6. Production reliability at scale — Thousands of jobs/sec with robust stalled-job recovery
  7. Multi-language teams — Same queues from Node.js, Python, Elixir, Rust, PHP
  8. 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:

  1. Need simplicity — ~1K LOC, up and running in 5 minutes
  2. Value minimal dependencies — Microservices where every dependency matters
  3. Prioritize raw throughput for simple jobs — Fire-and-forget tasks with minimal overhead
  4. Don't need scheduling, rate limits, or priorities — Straightforward job processing
  5. Want smaller memory footprint — Less metadata stored in Redis per job
  6. 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

  1. 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.

  2. BeeQueue excels where simplicity and minimal overhead matter — quick projects, simple job processing, and environments where ~1K LOC is preferable to ~50K LOC.

  3. Performance is not the differentiator — both are fast enough for most use cases. The real difference is in features.

  4. Migration from BeeQueue to BullMQ is straightforward but requires attention to retry-count semantics, connection handling, and event listener patterns.

  5. QueueHub provides unified monitoring for both libraries, making it the ideal dashboard during migration or in mixed environments.

  6. 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