bull vs agenda vs bee-queue vs bree vs kue vs node-resque
Node.js 向けバックグラウンドジョブキューライブラリの比較
bullagendabee-queuebreekuenode-resque類似パッケージ:

Node.js 向けバックグラウンドジョブキューライブラリの比較

これらのライブラリは、Node.js アプリケーションでメール送信、画像処理、データ集計などの重いタスクをバックグラウンドで非同期に実行するためのツールです。メインのリクエスト処理をブロックせずに済むため、ユーザー体験の向上やシステム全体の安定性に貢献します。Redis や MongoDB などの外部ストレージを利用してジョブを管理し、失敗時のリトライやスケジュール実行といった機能を提供します。

npmのダウンロードトレンド

3 年

GitHub Starsランキング

統計詳細

パッケージ
ダウンロード数
Stars
サイズ
Issues
公開日時
ライセンス
bull2,245,94716,262309 kB1472年前MIT
agenda228,6899,712310 kB1910時間前MIT
bee-queue67,7854,039107 kB461年前MIT
bree42,9493,29692 kB308ヶ月前MIT
kue31,0799,418-2869年前MIT
node-resque26,4421,415720 kB243ヶ月前Apache-2.0

Node.js バックグラウンドジョブ処理ライブラリの比較

Node.js でバックグラウンドタスクを処理する際、どのライブラリを選ぶかはシステムの信頼性に直結します。agenda、bee-queue、bree、bull、kue、node-resque はすべてジョブキューを管理しますが、内部の仕組みや適した場面が異なります。実務で直面する課題に基づき、主要な違いを解説します。

💾 ストレージの仕組み:MongoDB vs Redis vs ファイル

ジョブの状態をどこに保存するかは、インフラ構成に大きな影響を与えます。

agenda は MongoDB を使用します。

  • データベースにジョブを保存するため、Redis を別途用意する必要がありません。
  • 既存の MongoDB クラスターを流用できるため、インフラコストを抑えられます。
// agenda: MongoDB 接続設定
const agenda = new Agenda({ db: { address: 'mongodb://localhost/agenda' } });
await agenda.start();

bee-queue、bull、kue、node-resque は Redis を使用します。

  • 高速なインメモリ処理が可能で、読み書きのレイテンシが小さくなります。
  • すでに Redis をキャッシュなどで使っている場合、追加の学習コストが低いです。
// bull: Redis 接続設定
const Queue = require('bull');
const queue = new Queue('my-queue', 'redis://127.0.0.1:6379');

bree はファイルシステムとプロセス管理を重視します。

  • 外部のキューサーバーに依存せず、ローカルのスクリプトを直接実行できます。
  • 設定ファイルでジョブを定義するため、管理がシンプルです。
// bree: 設定ファイルベース
const Bree = require('bree');
const bree = new Bree({
  root: './jobs',
  jobs: [{ name: 'daily-report', interval: 'daily' }]
});

📝 ジョブの定義と実行方法

ジョブをどのように登録し、実行するかは開発体験に直結します。

agenda は名前付きジョブを定義します。

  • 文字列でジョブ名を指定し、その名前で呼び出します。
  • スケジュールと実行ロジックが明確に分離されています。
// agenda: ジョブ定義と実行
agenda.define('send email', async (job) => {
  console.log(`Sending to ${job.data.to}`);
});
await agenda.schedule('in 5 minutes', 'send email', { to: '[email protected]' });

bee-queue はキューインスタンスに対して処理を登録します。

  • キューごとに処理ロジックを紐付けるスタイルです。
  • コード量が少なく、直感的に理解しやすい構造です。
// bee-queue: 処理登録
queue.process(async (job) => {
  console.log(`Processing job ${job.id}`);
  return job.data;
});
const job = queue.createJob({ id: 123 });
await job.save();

bull もキューベースの処理登録を行います。

  • queue.process で処理を定義し、queue.add でジョブを投入します。
  • 戻り値や Promise の扱いが明確で、エラーハンドリングがしやすいです。
// bull: 処理登録と追加
queue.process(async (job) => {
  return doSomething(job.data);
});
await queue.add({ foo: 'bar' });

bree は独立したファイルとしてジョブを記述します。

  • ./jobs/daily-report.js のようなファイルを作成し、そこでロジックを書きます。
  • メインプロセスと分離されるため、メモリ管理が容易です。
// bree: ワーカースクリプト (jobs/daily-report.js)
module.exports = async () => {
  console.log('Running daily report');
};

kue はチェーンメソッドでジョブを作成します。

  • 古いスタイルの API で、ジョブ作成と保存を連続して行います。
  • 現在は非推奨ですが、既存コードではこの形式を見かけます。
// kue: ジョブ作成
const job = queue.create('email', { to: 'user' });
job.save((err) => {
  if (!err) console.log('Job saved');
});

node-resque はクラスベースでキューとワーカーを管理します。

  • Queue クラスで enqueue し、Worker クラスで処理します。
  • Resque プロトコルに準拠した明確な役割分担があります。
// node-resque: キューとワーカー
const { Worker, Queue } = require('node-resque');
const queue = new Queue({ connection: { host: 'localhost' } });
await queue.connect();
await queue.enqueue('queueName', 'jobName', ['arg1']);

🕒 スケジュールと定期実行

特定の時刻にジョブを実行したい場合、ライブラリの対応状況が重要です。

agenda はスケジュール機能が強力です。

  • schedule や every メソッドで、人間が読める形式の時間を指定できます。
  • 複雑な cron 表現よりも直感的な記述が可能です。
// agenda: スケジュール実行
await agenda.every('10 minutes', 'calculate stats');
await agenda.schedule('tomorrow at noon', 'send report');

bree は cron 構文や間隔指定をサポートします。

  • 設定ファイルに interval や cron を書くだけで済みます。
  • 定期的なバッチ処理に特化しているため、設定が最も簡単です。
// bree: 間隔指定
const bree = new Bree({
  jobs: [
    { name: 'cleanup', interval: '5m' },
    { name: 'backup', cron: '0 0 * * *' }
  ]
});

bull はジョブ追加時にオプションで指定します。

  • repeat オプションを使って、ジョブの繰り返しを設定します。
  • キューの中にスケジュール情報が含まれるため、管理が一元化されます。
// bull: 繰り返しジョブ
await queue.add('send notification', {}, {
  repeat: { cron: '*/5 * * * *' }
});

bee-queue、kue、node-resque は基本的なキュー機能に留まります。

  • 本体には高度なスケジューラーが含まれていないことが多いです。
  • 外部の cron ライブラリと組み合わせて使う必要があります。
// node-resque: 基本的な enqueue のみ
// スケジュール機能は本体にないため、外部で管理が必要
await queue.enqueue('high', 'sendEmail', ['[email protected]']);

⚠️ メンテナンス状況と将来性

ライブラリの保守状況は、長期的なプロジェクトの安定性を決めます。

kue は公式に非推奨です。

  • GitHub リポジトリはアーカイブされており、更新は停止しています。
  • セキュリティ脆弱性が見つかった場合でも修正されないため、新規採用は避けるべきです。
// kue: 使用を避けるべき
// const kue = require('kue'); // 非推奨

bull はメンテナンスモードに入っています。

  • 機能は安定していますが、新機能は後継の bullmq で開発されています。
  • 新規プロジェクトでは bullmq の検討を推奨しますが、bull もまだ広く使われています。
// bull: 後継に注意
// const Queue = require('bull'); // _legacy_
// const { Queue } = require('bullmq'); // _modern_

agenda、bee-queue、bree、node-resque は継続してメンテナンスされています。

  • 定期的なアップデートがあり、Node.js の新バージョンにも対応しています。
  • コミュニティのサポートも活発で、問題発生時の対応が比較的容易です。
// bree: 活発なメンテナンス
const Bree = require('bree'); // 最新版が利用可能

🛡️ エラーハンドリングと再試行

ジョブが失敗したときにどう振る舞うかは、システムの信頼性に影響します。

agenda は再試行ロジックを内蔵しています。

  • 失敗したジョブを自動的に再スケジュールする設定が可能です。
  • 最大再試行回数や間隔を細かく制御できます。
// agenda: 再試行設定
agenda.define('task', { priority: 'high', concurrency: 10 }, async (job) => {
  // 失敗時は自動的にリトライ設定に従う
});

bull は詳細なアトンプト設定を持っています。

  • attempts オプションで再試行回数を指定できます。
  • 失敗時のバックオフ(待機時間)設定も可能です。
// bull: アトンプト設定
queue.add('video process', { id: 1 }, {
  attempts: 3,
  backoff: { type: 'fixed', delay: 2000 }
});

bee-queue はシンプルにエラーを返します。

  • 処理関数でエラーをスローすると、ジョブは失敗としてマークされます。
  • 複雑な再試行制御はアプリケーション側で実装する必要があります。
// bee-queue: エラー処理
queue.process(async (job) => {
  if (!job.data.valid) throw new Error('Invalid data');
});

bree はワーカープロセスの再起動で対応します。

  • プロセスがクラッシュしても、メインプロセスがそれを検知して再起動します。
  • ジョブ自体の再試行というよりは、実行環境の回復に近いです。
// bree: ワーカー管理
bree.on('worker error', (name, err) => {
  console.error(`Worker ${name} failed`, err);
  // 必要に応じて手動で再起動トリガー
});

💡 結論

agenda は MongoDB を使っているチームに最適です — データベース統合がスムーズで、スケジュール機能が豊富です。

bull は Redis 環境で高機能なキューが必要な場合に適していますが、bullmq への移行を視野に入れるべきです — 機能は豊富ですが、レガシーになりつつあります。

bree は cron ジョブや定期タスクに特化しています — 別プロセス実行による安定性が魅力で、設定も簡単です。

bee-queue はシンプルさを求める場合に選びます — Redis を使いつつ、設定を最小限に抑えたいプロジェクトに向いています。

kue は使用してはいけません — 非推奨であり、セキュリティリスクがあります。

node-resque は Resque エコシステムとの互換性が必要な場合に意味があります — 特定の環境以外では、他のモダンな選択肢を検討すべきです。

最終的には、既存のインフラ(Redis か MongoDB か)と、求められる機能(単純なキューか、複雑なスケジュールか)で決定します。

選び方: bull vs agenda vs bee-queue vs bree vs kue vs node-resque

  • bull:

    Redis ベースで堅牢な機能セットが必要な場合に適していますが、後継の bullmq への移行も検討すべきです。ジョブの優先順位やイベント処理など、エンタープライズ向けの機能が充実しています。大規模な分散処理システムで実績が多いライブラリです。

  • agenda:

    MongoDB を既に使用しているプロジェクトで、ジョブの永続性と柔軟なスケジュール管理を重視する場合に選びます。データベースにジョブ状態を保存するため、Redis の導入が不要な点がメリットです。複雑な再試行ロジックや頻繁なスケジューリングが必要なシステムに適しています。

  • bee-queue:

    Redis を使ったシンプルで軽量なキュー構成が必要な場合に適しています。設定項目が少なく、コードが直感的であるため、小規模から中規模のタスク処理に最適です。高度な機能よりも、導入の手軽さと動作の確実性を優先するプロジェクトで選ばれます。

  • bree:

    cron ジョブや定期的なタスク実行をメインの目的とする場合に選びます。他のキューライブラリとは異なり、ワーカーを別プロセスとして実行するため、メモリリークのリスクを隔離できます。Node.js スクリプトを直接ジョブとして登録できるため、開発者の負担が少ないのが特徴です。

  • kue:

    新規プロジェクトでは使用しないでください。このライブラリは公式に非推奨(Deprecated)となっており、メンテナンスが停止しています。セキュリティリスクやバグ修正が受けられないため、既存システムからの移行を強く推奨します。

  • node-resque:

    Ruby の Resque システムと互換性が必要な場合や、Resque プロトコルに基づく既存インフラと連携する場合に選びます。Redis をバックエンドに使用し、シンプルな FIFO キュー構成を提供します。特定のレガシーシステムとの統合が必要なシーンで価値を発揮します。

bull のREADME




The fastest, most reliable, Redis-based queue for Node.
Carefully written for rock solid stability and atomicity.


Sponsors · Features · UIs · Install · Quick Guide · Documentation

Check the new Guide!


🚀 Sponsors 🚀

Dragonfly Dragonfly is a new Redis™ drop-in replacement that is fully compatible with BullMQ and brings some important advantages over Redis™ such as massive better performance by utilizing all CPU cores available and faster and more memory efficient data structures. Read more here on how to use it with BullMQ.

📻 News and updates

Bull is currently in maintenance mode, we are only fixing bugs. For new features check BullMQ, a modern rewritten implementation in Typescript. You are still very welcome to use Bull if it suits your needs, which is a safe, battle tested library.

Follow me on Twitter for other important news and updates.

🛠 Tutorials

You can find tutorials and news in this blog: https://blog.taskforce.sh/


Used by

Bull is popular among large and small organizations, like the following ones:

Atlassian Autodesk Mozilla Nest Salesforce


Official FrontEnd

Taskforce.sh, Inc

Supercharge your queues with a professional front end:

  • Get a complete overview of all your queues.
  • Inspect jobs, search, retry, or promote delayed jobs.
  • Metrics and statistics.
  • and many more features.

Sign up at Taskforce.sh


Bull Features

  • Minimal CPU usage due to a polling-free design.
  • Robust design based on Redis.
  • Delayed jobs.
  • Schedule and repeat jobs according to a cron specification.
  • Rate limiter for jobs.
  • Retries.
  • Priority.
  • Concurrency.
  • Pause/resume—globally or locally.
  • Multiple job types per queue.
  • Threaded (sandboxed) processing functions.
  • Automatic recovery from process crashes.

And coming up on the roadmap...

  • Job completion acknowledgement (you can use the message queue pattern in the meantime).
  • Parent-child jobs relationships.

UIs

There are a few third-party UIs that you can use for monitoring:

BullMQ

Bull v3

Bull <= v2


Monitoring & Alerting


Feature Comparison

Since there are a few job queue solutions, here is a table comparing them:

FeatureBullMQ-ProBullMQBullKueBeeAgenda
Backendredisredisredisredisredismongo
Observables✓
Group Rate Limit✓
Group Support✓
Batches Support✓
Parent/Child Dependencies✓✓
Priorities✓✓✓✓✓
Concurrency✓✓✓✓✓✓
Delayed jobs✓✓✓✓✓
Global events✓✓✓✓
Rate Limiter✓✓✓
Pause/Resume✓✓✓✓
Sandboxed worker✓✓✓
Repeatable jobs✓✓✓✓
Atomic ops✓✓✓✓
Persistence✓✓✓✓✓✓
UI✓✓✓✓✓
Optimized forJobs / MessagesJobs / MessagesJobs / MessagesJobsMessagesJobs

Install

npm install bull --save

or

yarn add bull

Requirements: Bull requires a Redis version greater than or equal to 2.8.18.

Typescript Definitions

npm install @types/bull --save-dev
yarn add --dev @types/bull

Definitions are currently maintained in the DefinitelyTyped repo.

Contributing

We welcome all types of contributions, either code fixes, new features or doc improvements. Code formatting is enforced by prettier. For commits please follow conventional commits convention. All code must pass lint rules and test suites before it can be merged into develop.


Quick Guide

Basic Usage

const Queue = require('bull');

const videoQueue = new Queue('video transcoding', 'redis://127.0.0.1:6379');
const audioQueue = new Queue('audio transcoding', { redis: { port: 6379, host: '127.0.0.1', password: 'foobared' } }); // Specify Redis connection using object
const imageQueue = new Queue('image transcoding');
const pdfQueue = new Queue('pdf transcoding');

videoQueue.process(function (job, done) {

  // job.data contains the custom data passed when the job was created
  // job.id contains id of this job.

  // transcode video asynchronously and report progress
  job.progress(42);

  // call done when finished
  done();

  // or give an error if error
  done(new Error('error transcoding'));

  // or pass it a result
  done(null, { framerate: 29.5 /* etc... */ });

  // If the job throws an unhandled exception it is also handled correctly
  throw new Error('some unexpected error');
});

audioQueue.process(function (job, done) {
  // transcode audio asynchronously and report progress
  job.progress(42);

  // call done when finished
  done();

  // or give an error if error
  done(new Error('error transcoding'));

  // or pass it a result
  done(null, { samplerate: 48000 /* etc... */ });

  // If the job throws an unhandled exception it is also handled correctly
  throw new Error('some unexpected error');
});

imageQueue.process(function (job, done) {
  // transcode image asynchronously and report progress
  job.progress(42);

  // call done when finished
  done();

  // or give an error if error
  done(new Error('error transcoding'));

  // or pass it a result
  done(null, { width: 1280, height: 720 /* etc... */ });

  // If the job throws an unhandled exception it is also handled correctly
  throw new Error('some unexpected error');
});

pdfQueue.process(function (job) {
  // Processors can also return promises instead of using the done callback
  return pdfAsyncProcessor();
});

videoQueue.add({ video: 'http://example.com/video1.mov' });
audioQueue.add({ audio: 'http://example.com/audio1.mp3' });
imageQueue.add({ image: 'http://example.com/image1.tiff' });

Using promises

Alternatively, you can return promises instead of using the done callback:

videoQueue.process(function (job) { // don't forget to remove the done callback!
  // Simply return a promise
  return fetchVideo(job.data.url).then(transcodeVideo);

  // Handles promise rejection
  return Promise.reject(new Error('error transcoding'));

  // Passes the value the promise is resolved with to the "completed" event
  return Promise.resolve({ framerate: 29.5 /* etc... */ });

  // If the job throws an unhandled exception it is also handled correctly
  throw new Error('some unexpected error');
  // same as
  return Promise.reject(new Error('some unexpected error'));
});

Separate processes

The process function can also be run in a separate process. This has several advantages:

  • The process is sandboxed so if it crashes it does not affect the worker.
  • You can run blocking code without affecting the queue (jobs will not stall).
  • Much better utilization of multi-core CPUs.
  • Less connections to redis.

In order to use this feature just create a separate file with the processor:

// processor.js
module.exports = function (job) {
  // Do some heavy work

  return Promise.resolve(result);
}

And define the processor like this:

// Single process:
queue.process('/path/to/my/processor.js');

// You can use concurrency as well:
queue.process(5, '/path/to/my/processor.js');

// and named processors:
queue.process('my processor', 5, '/path/to/my/processor.js');

Repeated jobs

A job can be added to a queue and processed repeatedly according to a cron specification:

  paymentsQueue.process(function (job) {
    // Check payments
  });

  // Repeat payment job once every day at 3:15 (am)
  paymentsQueue.add(paymentsData, { repeat: { cron: '15 3 * * *' } });

As a tip, check your expressions here to verify they are correct: cron expression generator

Pause / Resume

A queue can be paused and resumed globally (pass true to pause processing for just this worker):

queue.pause().then(function () {
  // queue is paused now
});

queue.resume().then(function () {
  // queue is resumed now
})

Events

A queue emits some useful events, for example...

.on('completed', function (job, result) {
  // Job completed with output result!
})

For more information on events, including the full list of events that are fired, check out the Events reference

Queues performance

Queues are cheap, so if you need many of them just create new ones with different names:

const userJohn = new Queue('john');
const userLisa = new Queue('lisa');
.
.
.

However every queue instance will require new redis connections, check how to reuse connections or you can also use named processors to achieve a similar result.

Cluster support

NOTE: From version 3.2.0 and above it is recommended to use threaded processors instead.

Queues are robust and can be run in parallel in several threads or processes without any risk of hazards or queue corruption. Check this simple example using cluster to parallelize jobs across processes:

const Queue = require('bull');
const cluster = require('cluster');

const numWorkers = 8;
const queue = new Queue('test concurrent queue');

if (cluster.isMaster) {
  for (let i = 0; i < numWorkers; i++) {
    cluster.fork();
  }

  cluster.on('online', function (worker) {
    // Let's create a few jobs for the queue workers
    for (let i = 0; i < 500; i++) {
      queue.add({ foo: 'bar' });
    };
  });

  cluster.on('exit', function (worker, code, signal) {
    console.log('worker ' + worker.process.pid + ' died');
  });
} else {
  queue.process(function (job, jobDone) {
    console.log('Job done by worker', cluster.worker.id, job.id);
    jobDone();
  });
}

Documentation

For the full documentation, check out the reference and common patterns:

  • Guide — Your starting point for developing with Bull.
  • Reference — Reference document with all objects and methods available.
  • Patterns — a set of examples for common patterns.
  • License — the Bull license—it's MIT.

If you see anything that could use more docs, please submit a pull request!


Important Notes

The queue aims for an "at least once" working strategy. This means that in some situations, a job could be processed more than once. This mostly happens when a worker fails to keep a lock for a given job during the total duration of the processing.

When a worker is processing a job it will keep the job "locked" so other workers can't process it.

It's important to understand how locking works to prevent your jobs from losing their lock - becoming stalled - and being restarted as a result. Locking is implemented internally by creating a lock for lockDuration on interval lockRenewTime (which is usually half lockDuration). If lockDuration elapses before the lock can be renewed, the job will be considered stalled and is automatically restarted; it will be double processed. This can happen when:

  1. The Node process running your job processor unexpectedly terminates.
  2. Your job processor was too CPU-intensive and stalled the Node event loop, and as a result, Bull couldn't renew the job lock (see #488 for how we might better detect this). You can fix this by breaking your job processor into smaller parts so that no single part can block the Node event loop. Alternatively, you can pass a larger value for the lockDuration setting (with the tradeoff being that it will take longer to recognize a real stalled job).

As such, you should always listen for the stalled event and log this to your error monitoring system, as this means your jobs are likely getting double-processed.

As a safeguard so problematic jobs won't get restarted indefinitely (e.g. if the job processor always crashes its Node process), jobs will be recovered from a stalled state a maximum of maxStalledCount times (default: 1).