agenda、bee-queue、bull、bullmq、kue、node-resque はすべて Node.js で非同期ジョブやバックグラウンドタスクを管理するためのライブラリです。これらはジョブをキューに登録し、ワーカープロセスが非同期で処理することで、メール送信、ファイル変換、データ同期などの重い処理をメインアプリケーションから切り離すことを可能にします。各ライブラリはデータストア(MongoDB または Redis)や API 設計、再試行・遅延実行のサポート、保守状況において大きく異なり、アーキテクチャ選定の際に重要な判断材料となります。
Node.js アプリケーションで非同期処理やバックグラウンドジョブを扱う際、信頼性の高いジョブキュー管理ライブラリは不可欠です。本稿では、agenda、bee-queue、bull、bullmq、kue、node-resque の6つの主要なnpmパッケージを、実際の開発現場での使用観点から技術的に比較します。各ライブラリの設計思想、Redis依存の有無、APIスタイル、再試行・遅延実行のサポート、そして保守状況に焦点を当て、プロフェッショナルなフロントエンド開発者がアーキテクチャ選定を行う際に役立つ情報を提供します。
これらのライブラリはすべて「ジョブ(タスク)をキューに登録し、ワーカーが非同期で処理する」という基本的なパターンを実現しますが、裏側で使用するデータストアが大きく異なります。この点がアーキテクチャ選定の最も重要な分岐点です。
agendaagenda は MongoDB 専用のジョブスケジューラです。MongoDB が既にシステムに導入済みで、追加のインフラ(Redisなど)を避けたい場合に最適です。
// agenda: MongoDB にジョブを保存
const Agenda = require('agenda');
const agenda = new Agenda({ db: { address: 'mongodb://localhost:27017/agenda' } });
agenda.define('send email', async (job) => {
await sendEmail(job.attrs.data.to, job.attrs.data.subject);
});
await agenda.start();
agenda.now('send email', { to: 'user@example.com', subject: 'Hello' });
bee-queue、bull、bullmq、kue、node-resque はすべて Redis をデータストアとして使用します。Redis はメモリ上にデータを保持し、高速なキュー処理が可能ですが、永続化設定やクラスタリングなどの運用負荷が伴います。
// bee-queue: Redis を使用
const Queue = require('bee-queue');
const queue = new Queue('email', { redis: { host: 'localhost', port: 6379 } });
queue.process(async (job) => {
return await sendEmail(job.data.to, job.data.subject);
});
queue.createJob({ to: 'user@example.com', subject: 'Hello' }).save();
💡 重要なポイント: MongoDB と Redis のどちらを使うかは、既存のインフラ、チームの運用経験、ジョブの重要度(失敗許容度)によって決まります。
agenda以外は Redis 必須です。
公式情報に基づき、以下のライブラリは 新規プロジェクトでの使用を避けるべきです。
kue: GitHub リポジトリおよび npm ページに明確な非推奨(deprecated)の記載があります。最後の更新は2018年であり、メンテナンスされていません。node-resque: 公式 npm ページに「This package is deprecated. Please use @resque/resque instead.」と記載されています。ただし、@resque/resque は別パッケージのため、ここでは node-resque は非推奨と判断します。✅ 結論: 新規プロジェクトでは
kueとnode-resqueは使用しないでください。代わりにbull、bullmq、bee-queue、agendaを検討しましょう。
ジョブが失敗したときの再試行や、特定時間後に実行する遅延ジョブは、多くのアプリケーションで必要です。各ライブラリの実装方法を比較します。
agenda: スケジューリング機能内蔵agenda は MongoDB の日付クエリを使って、自然な形で遅延と再試行を実現します。
// agenda: 失敗時に最大3回再試行、10分後に再実行
agenda.define('process payment', { retry: 3, retryDelay: 10 * 60 * 1000 }, async (job) => {
// ...
});
// 5分後に実行
agenda.schedule('in 5 minutes', 'process payment', { orderId: 123 });
bee-queue: シンプルで軽量bee-queue は再試行回数を指定できますが、遅延実行は直接サポートしていません(代替手段が必要)。
// bee-queue: 最大2回再試行
const queue = new Queue('task', { activateDelayedJobs: true });
queue.process({ concurrency: 5, retryLimit: 2 }, async (job) => {
// ...
});
// 遅延実行は非サポート(workaround 必要)
bull / bullmq: 機能豊富で柔軟bull と bullmq は再試行、遅延、優先度、レート制限など、ほぼすべての高度な機能をサポートしています。
// bull: 再試行と遅延
const Queue = require('bull');
const queue = new Queue('video transcoding');
queue.process(async (job) => {
// ...
});
// 遅延付きジョブ(5秒後)
queue.add({ videoId: 123 }, { delay: 5000 });
// 再試行設定(指数バックオフ)
queue.add({ videoId: 123 }, {
attempts: 3,
backoff: { type: 'exponential', delay: 1000 }
});
// bullmq: 同様の機能(APIがよりモダン)
import { Queue, Worker } from 'bullmq';
const queue = new Queue('audio');
const worker = new Worker('audio', async (job) => { /* ... */ });
await queue.add('transcode', { file: 'song.mp3' }, {
delay: 5000,
attempts: 3,
backoff: { type: 'exponential', delay: 1000 }
});
💡 ポイント: 複雑なスケジューリングや再試行戦略が必要なら、
bullまたはbullmqが最適です。bee-queueはシンプルさを重視するケース向けです。
bull vs bullmq: 古典的 vs モダンbull はコールバックとPromiseを混在させた古典的なAPIですが、bullmq は完全にPromise/async-await対応で、TypeScriptサポートも優れています。
// bull: クラシックなAPI
const queue = new Bull('tasks');
queue.process((job, done) => {
// 処理後に done() を呼ぶ
done(null, result);
});
// bullmq: モダンなasync/await
const worker = new Worker('tasks', async (job) => {
return await process(job.data);
});
✅ 推奨: 新規プロジェクトでは
bullmqを選ぶのが安全です。bullはまだ使われていますが、bullmqが公式に推奨される次世代版です。
bee-queue: 最小限のAPIbee-queue は「キュー作成 → ジョブ登録 → 処理」の流れが非常にシンプルで、学習コストが低いです。
const queue = new Queue('simple');
queue.process(async (job) => { /* ... */ });
queue.createJob(data).save();
運用中にジョブの状態を確認できるUIがあると便利です。
bull / bullmq: 公式の bull-board というUIツールがあり、簡単に統合できます。agenda: サードパーティ製の agenda-ui がありますが、公式サポートではありません。bee-queue: 公式UIなし。監視にはカスタムスクリプトが必要です。| ライブラリ | データストア | 非推奨 | 特徴 |
|---|---|---|---|
agenda | MongoDB | ❌ | MongoDBユーザー向け、スケジューリング内蔵 |
bee-queue | Redis | ❌ | 軽量・シンプル、高度機能少なめ |
bull | Redis | ❌ | 機能豊富、UIあり、古めのAPI |
bullmq | Redis | ❌ | bull の次世代版、モダンAPI、TypeScript対応 |
kue | Redis | ✅ | 非推奨、新規プロジェクトで使用禁止 |
node-resque | Redis | ✅ | 非推奨、@resque/resque へ移行推奨 |
agenda を選ぶ。bee-queue を選ぶ。bullmq を選ぶ(bull はレガシー案件向け)。kue や node-resque は絶対に新規で使わない — 公式で非推奨と明記されています。これらのライブラリはどれも「ジョブをキューに入れて処理する」という同じゴールを持ちますが、裏側の設計思想と機能セットは大きく異なります。あなたのプロジェクトのインフラ、チームのスキル、将来の拡張性を考慮して、慎重に選んでください。
MongoDB を既に使用しており、追加のインフラ(Redisなど)を避けたい場合に最適です。スケジューリング機能が内蔵されており、日時指定のジョブ実行が自然に実装できます。ただし、Redis を使いたい場合は選択肢から外れます。
軽量でシンプルなAPIを求める場合に適しています。Redis を使用しますが、高度な機能(遅延実行、複雑な再試行戦略)はサポートしておらず、最小限の依存でジョブキューを実現したいプロジェクトに向いています。
Redis を使い、UIダッシュボードや豊富な機能(再試行、遅延、優先度)が必要なレガシーまたは既存プロジェクト向けです。ただし、APIが古く、新規プロジェクトでは bullmq の方が推奨されます。
Redis を使い、最新の async/await 対応、TypeScript サポート、高度なジョブ管理機能(指数バックオフ、レート制限、UI統合)を求める新規プロジェクトに最適です。bull の公式後継として積極的に開発されています。
非推奨のライブラリです。GitHub および npm で明確に deprecated と記載されており、最後の更新は2018年です。新規プロジェクトでは絶対に使用せず、代わりに bullmq や bee-queue を検討してください。
非推奨のライブラリです。npm ページに「This package is deprecated. Please use @resque/resque instead.」と記載されています。新規プロジェクトでは使用せず、代替パッケージを検討する必要があります。
A light-weight job scheduling library for Node.js
Migrating from v5? See the Migration Guide for all breaking changes.
Agenda 6.x is a complete TypeScript rewrite with a focus on modularity and flexibility:
Pluggable storage backends - Choose from MongoDB, PostgreSQL, Redis, or implement your own. Each backend is a separate package - install only what you need.
Pluggable notification channels - Move beyond polling with real-time job notifications via Redis, PostgreSQL LISTEN/NOTIFY, or other pub/sub systems. Jobs get processed immediately when saved, not on the next poll cycle.
Modern stack - ESM-only, Node.js 18+, full TypeScript with strict typing.
See the 6.x Roadmap for details and progress.
Install the core package and your preferred backend:
# For MongoDB
npm install agenda @agendajs/mongo-backend
# For PostgreSQL
npm install agenda @agendajs/postgres-backend
# For Redis
npm install agenda @agendajs/redis-backend
Requirements:
import { Agenda } from 'agenda';
import { MongoBackend } from '@agendajs/mongo-backend';
const agenda = new Agenda({
backend: new MongoBackend({ address: 'mongodb://localhost/agenda' })
});
// Define a job
agenda.define('send email', async (job) => {
const { to, subject } = job.attrs.data;
await sendEmail(to, subject);
});
// Start processing
await agenda.start();
// Schedule jobs
await agenda.every('1 hour', 'send email', { to: 'user@example.com', subject: 'Hello' });
await agenda.schedule('in 5 minutes', 'send email', { to: 'admin@example.com', subject: 'Report' });
await agenda.now('send email', { to: 'support@example.com', subject: 'Urgent' });
| Package | Backend | Notifications | Install |
|---|---|---|---|
@agendajs/mongo-backend | MongoDB | Polling only | npm install @agendajs/mongo-backend |
@agendajs/postgres-backend | PostgreSQL | LISTEN/NOTIFY | npm install @agendajs/postgres-backend |
@agendajs/redis-backend | Redis | Pub/Sub | npm install @agendajs/redis-backend |
| Backend | Storage | Notifications | Notes |
|---|---|---|---|
MongoDB (MongoBackend) | ✅ | ❌ | Storage only. Combine with external notification channel for real-time. |
PostgreSQL (PostgresBackend) | ✅ | ✅ | Full backend. Uses LISTEN/NOTIFY for notifications. |
Redis (RedisBackend) | ✅ | ✅ | Full backend. Uses Pub/Sub for notifications. |
| InMemoryNotificationChannel | ❌ | ✅ | Notifications only. For single-process/testing. |
import { Agenda } from 'agenda';
import { MongoBackend } from '@agendajs/mongo-backend';
// Via connection string
const agenda = new Agenda({
backend: new MongoBackend({ address: 'mongodb://localhost/agenda' })
});
// Via existing MongoDB connection
const agenda = new Agenda({
backend: new MongoBackend({ mongo: existingDb })
});
// With options
const agenda = new Agenda({
backend: new MongoBackend({
mongo: db,
collection: 'jobs' // Collection name (default: 'agendaJobs')
}),
processEvery: '30 seconds', // Job polling interval
maxConcurrency: 20, // Max concurrent jobs
defaultConcurrency: 5 // Default per job type
});
import { Agenda } from 'agenda';
import { PostgresBackend } from '@agendajs/postgres-backend';
const agenda = new Agenda({
backend: new PostgresBackend({
connectionString: 'postgresql://user:pass@localhost:5432/mydb'
})
});
import { Agenda } from 'agenda';
import { RedisBackend } from '@agendajs/redis-backend';
const agenda = new Agenda({
backend: new RedisBackend({
connectionString: 'redis://localhost:6379'
})
});
For faster job processing across distributed systems:
import { Agenda, InMemoryNotificationChannel } from 'agenda';
import { MongoBackend } from '@agendajs/mongo-backend';
const agenda = new Agenda({
backend: new MongoBackend({ mongo: db }),
notificationChannel: new InMemoryNotificationChannel()
});
You can use MongoDB for storage while using a different system for real-time notifications:
import { Agenda } from 'agenda';
import { MongoBackend } from '@agendajs/mongo-backend';
import { RedisBackend } from '@agendajs/redis-backend';
// MongoDB for storage + Redis for real-time notifications
const redisBackend = new RedisBackend({ connectionString: 'redis://localhost:6379' });
const agenda = new Agenda({
backend: new MongoBackend({ mongo: db }),
notificationChannel: redisBackend.notificationChannel
});
This is useful when you want MongoDB's proven durability and flexible queries for job storage, but need faster real-time notifications across multiple processes.
// Simple async handler
agenda.define('my-job', async (job) => {
console.log('Processing:', job.attrs.data);
});
// With options
agenda.define('my-job', async (job) => { /* ... */ }, {
concurrency: 10,
lockLimit: 5,
lockLifetime: 10 * 60 * 1000, // 10 minutes
priority: 'high'
});
For a class-based approach, use TypeScript decorators:
import { JobsController, Define, Every, registerJobs, Job } from 'agenda';
@JobsController({ namespace: 'email' })
class EmailJobs {
@Define({ concurrency: 5 })
async sendWelcome(job: Job<{ userId: string }>) {
console.log('Sending welcome to:', job.attrs.data.userId);
}
@Every('1 hour')
async cleanupBounced(job: Job) {
console.log('Cleaning up bounced emails');
}
}
registerJobs(agenda, [new EmailJobs()]);
await agenda.start();
// Schedule using namespaced name
await agenda.now('email.sendWelcome', { userId: '123' });
See Decorators Documentation for full details.
// Run immediately
await agenda.now('my-job', { userId: '123' });
// Run at specific time
await agenda.schedule('tomorrow at noon', 'my-job', data);
await agenda.schedule(new Date('2024-12-25'), 'my-job', data);
// Run repeatedly
await agenda.every('5 minutes', 'my-job');
await agenda.every('0 * * * *', 'my-job'); // Cron syntax
// Cancel jobs matching a filter (removes from database)
await agenda.cancel({ name: 'my-job' });
await agenda.cancel({ name: 'my-job', data: { userId: 123 } });
// Cancel ALL jobs unconditionally
await agenda.cancelAll();
// Disable/enable jobs globally (by query)
await agenda.disable({ name: 'my-job' }); // Disable all jobs matching query
await agenda.enable({ name: 'my-job' }); // Enable all jobs matching query
// Disable/enable individual jobs
const job = await agenda.create('my-job', data);
job.disable();
await job.save();
// Progress tracking
agenda.define('long-job', async (job) => {
for (let i = 0; i <= 100; i += 10) {
await doWork();
await job.touch(i); // Report progress 0-100
}
});
// Stop immediately - unlocks running jobs so other workers can pick them up
await agenda.stop();
// Drain - waits for running jobs to complete before stopping
await agenda.drain();
// Drain with timeout (30 seconds) - for cloud platforms with shutdown deadlines
const result = await agenda.drain(30000);
if (result.timedOut) {
console.log(`${result.running} jobs still running after timeout`);
}
// Drain with AbortSignal - for external control
const controller = new AbortController();
setTimeout(() => controller.abort(), 30000);
await agenda.drain({ signal: controller.signal });
Use drain() for graceful shutdowns where you want in-progress jobs to finish their work.
agenda.on('start', (job) => console.log('Job started:', job.attrs.name));
agenda.on('complete', (job) => console.log('Job completed:', job.attrs.name));
agenda.on('success', (job) => console.log('Job succeeded:', job.attrs.name));
agenda.on('fail', (err, job) => console.log('Job failed:', job.attrs.name, err));
// Job-specific events
agenda.on('start:send email', (job) => { /* ... */ });
agenda.on('fail:send email', (err, job) => { /* ... */ });
For databases other than MongoDB, PostgreSQL, or Redis, implement AgendaBackend:
import { AgendaBackend, JobRepository } from 'agenda';
class SQLiteBackend implements AgendaBackend {
readonly repository: JobRepository;
readonly notificationChannel = undefined; // Or implement NotificationChannel
async connect() { /* ... */ }
async disconnect() { /* ... */ }
}
const agenda = new Agenda({
backend: new SQLiteBackend({ path: './jobs.db' })
});
See Custom Backend Driver for details.
Official Backend Packages:
Tools:
MIT