agenda vs bee-queue vs bull vs bullmq vs kue
Node.js におけるバックグラウンドジョブキューの選定
agendabee-queuebullbullmqkue類似パッケージ:

Node.js におけるバックグラウンドジョブキューの選定

agendabee-queuebullbullmqkue は、Node.js アプリケーションで非同期タスクやバックグラウンドジョブを管理するためのライブラリです。これらは、メール送信、データ処理、スケジュール実行など、リクエストの応答を待たずに実行すべき処理をキューに格納し、ワーカーが順次処理する仕組みを提供します。agenda は MongoDB をストレージに使用し、柔軟なスケジューリングが強みです。bullbullmq は Redis を基盤とし、堅牢性とパフォーマンスに優れています。特に bullmq は最新の Redis Streams 技術を採用した次世代版です。bee-queue は最小限の設計で Redis を使用し、kue は歴史的に人気がありましたが、現在はメンテナンスが停止しています。

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

3 年

GitHub Starsランキング

統計詳細

パッケージ
ダウンロード数
Stars
サイズ
Issues
公開日時
ライセンス
agenda09,666297 kB1016日前MIT
bee-queue04,029107 kB486ヶ月前MIT
bull016,246309 kB1501年前MIT
bullmq08,9142.16 MB39314時間前MIT
kue09,444-2889年前MIT

Node.js ジョブキューの深層比較:アーキテクチャ、パフォーマンス、保守性

Node.js でバックグラウンドタスクを処理する際、agendabee-queuebullbullmqkue は代表的な選択肢ですが、それぞれ内部アーキテクチャと設計思想が異なります。フロントエンドアーキテクトがバックエンドのジョブ処理を選定する際は、単なる機能比較だけでなく、インフラ依存(Redis vs MongoDB)と長期的な保守性を考慮する必要があります。

🗄️ ストレージアーキテクチャ:Redis vs MongoDB

ジョブキューの心臓部は、ジョブデータをどこに保存するかです。これがインフラ構成に直結します。

agendaMongoDB を使用します。

  • データベースが既に MongoDB なら追加インフラが不要です。
  • クエリが柔軟で、ジョブの検索や管理が SQL や NoSQL クエリで行えます。
// agenda: MongoDB 接続を必要とする
const agenda = new Agenda({ db: { address: 'mongodb://localhost/agenda' } });
await agenda.start();

bullbullmqbee-queuekueRedis を使用します。

  • Redis はインメモリデータベースのため、読み書きが非常に高速です。
  • 既にキャッシュ層で Redis を使っている場合、インフラを統合できます。
// bull: Redis 接続を必要とする
const Queue = require('bull');
const queue = new Queue('my-queue', 'redis://127.0.0.1:6379');
// bullmq: Redis 接続(Streams 対応)
const { Queue } = require('bullmq');
const queue = new Queue('my-queue', { connection: { host: '127.0.0.1', port: 6379 } });
// bee-queue: 最小限の Redis 設定
const Queue = require('bee-queue');
const queue = new Queue('my-queue', 'redis://127.0.0.1:6379');
// kue: Redis 接続(レガシー)
const kue = require('kue');
const queue = kue.createQueue();

📝 ジョブの定義と処理:実装スタイルの違い

各ライブラリで「ジョブをキューに追加する」および「ジョブを処理する」コードがどう異なるか確認します。

agenda は、ジョブ定義とスケジューリングが分離しています。

// agenda: ジョブ定義
agenda.define('send email', async (job) => {
  await sendEmail(job.attrs.data.to);
});

// agenda: ジョブ実行
await agenda.now('send email', { to: 'user@example.com' });

bull は、キューインスタンスに対して addprocess を使用します。

// bull: ジョブ追加
await queue.add({ email: 'user@example.com' });

// bull: ジョブ処理
queue.process(async (job) => {
  await sendEmail(job.data.email);
});

bullmq は、QueueWorker を明確に分離したモダンな設計です。

// bullmq: ジョブ追加
await queue.add('send-email', { email: 'user@example.com' });

// bullmq: ジョブ処理(Worker を使用)
const worker = new Worker('my-queue', async (job) => {
  await sendEmail(job.data.email);
}, { connection: { host: '127.0.0.1', port: 6379 } });

bee-queue は、bull に似たシンプルな API を持ちます。

// bee-queue: ジョブ追加
const job = queue.createJob({ email: 'user@example.com' });
await job.save();

// bee-queue: ジョブ処理
queue.process(async (job) => {
  await sendEmail(job.data.email);
});

kue は、古いスタイルの API を使用します。

// kue: ジョブ追加
const job = queue.create('send email', { to: 'user@example.com' });
job.save();

// kue: ジョブ処理
queue.process('send email', async (job, done) => {
  await sendEmail(job.data.to);
  done();
});

⚙️ 高度な機能:再試行、遅延、優先度

プロダクション環境では、失敗したジョブの再試行や、特定時間後の実行(遅延)が不可欠です。

agenda は、MongoDB のクエリ能力を活かし、複雑な再試行ロジックを柔軟に記述できます。

// agenda: 失敗時の再試行設定
job.fail('connection error');
job.save(); // 状態が更新され、再試行ロジックで拾われる

bull は、オプションで簡単に再試行回数を指定できます。

// bull: 3 回再試行、5 秒遅延
await queue.add({ email: 'user@example.com' }, {
  attempts: 3,
  backoff: { type: 'fixed', delay: 5000 }
});

bullmq は、より詳細なバックオフ戦略をサポートします。

// bullmq: 指数関数的バックオフ
await queue.add('send-email', { email: 'user@example.com' }, {
  attempts: 3,
  backoff: { type: 'exponential', delay: 1000 }
});

bee-queue は、機能セットが絞られており、基本的な再試行のみサポートします。

// bee-queue: 単純な再試行
const job = queue.createJob({ email: 'user@example.com' });
job.retries(2);
await job.save();

kue も再試行をサポートしていますが、設定がやや冗長です。

// kue: 失敗時の再試行
job.on('failure', () => {
  job.attempts(3);
  job.save();
});

🛠️ 保守性とエコシステム:ダッシュボードとコミュニティ

ジョブキューは、実行状況の可視化(ダッシュボード)が運用上で重要です。

bullbullmq は、bull-board という素晴らしい UI ライブラリが公式にサポートされています。

  • キューの滞留状況、失敗したジョブの再実行などが GUI で可能です。
// bull-board: UI のセットアップ例
const { createBullBoard } = require('@bull-board/api');
const { BullMQAdapter } = require('@bull-board/api/bullMQAdapter');
// 設定省略...

agenda は、agenda-ui などのサードパーティ製ダッシュボードが存在しますが、bull ほど統合されていません。

kue は、組み込みの UI (kue-ui) がありましたが、メンテナンス停止に伴いセキュリティリスクがあります。

bee-queue は、公式の UI は提供されておらず、監視は自作またはサードパーティ製ツールに依存します。

📊 比較サマリー

機能agendabullbullmqbee-queuekue
ストレージMongoDBRedisRedis (Streams)RedisRedis
メンテナンス✅ 活発✅ 安定✅ 活発 (推奨)⚠️ 最小限❌ 停止
UI ダッシュボード⚠️ 限定的✅ 充実 (bull-board)✅ 充実 (bull-board)❌ 非公式⚠️ 古い
スケジューリング✅ 強力 (cron)⚠️ プラグイン依存✅ 内蔵 (Delayed)❌ 非対応⚠️ 限定的
パフォーマンス🟡 普通 (DB 依存)🟢 高速🟢 非常に高速🟢 高速🟡 普通

💡 結論:どのパッケージを選ぶべきか

kue は歴史的な重要性はありますが、現在はメンテナンスが停止しているため、新規プロジェクトでは絶対に使用しないでください。セキュリティ修正が行われないリスクがあります。

bee-queue は非常に軽量ですが、機能不足とメンテナンスの不透明さから、特別な理由(極限まで依存を減らしたい等)がない限り避けるべきです。

agenda は、既に MongoDB を使用しており、Redis を追加したくない場合や、複雑な cron スケジューリングをデータベース内で完結させたい場合に有効な選択肢です。

bull は非常に安定しており、多くのレガシーシステムで採用されています。しかし、新規プロジェクトでは後継である bullmq を選ぶのが賢明です。

bullmq は、Redis Streams を利用してより堅牢なアーキテクチャを実現しており、モダンな Node.js アプリケーションにおけるジョブキューのデファクトスタンダードになりつつあります。パフォーマンス、機能、コミュニティサポートのバランスが最も優れています。

最終的な推奨

  • Redis ありbullmq を選択。
  • MongoDB のみagenda を選択。
  • レガシー維持bull または kue(移行を計画)。

ジョブキューはアプリケーションの心臓部となるため、長期的なメンテナンスとコミュニティの活発さを最優先して選定してください。

選び方: agenda vs bee-queue vs bull vs bullmq vs kue

  • agenda:

    既存のインフラに MongoDB を使用しており、複雑なスケジューリング(cron 形式など)やジョブの再試行ロジックをデータベース側で管理したい場合に選択します。Redis の導入コストを避けたいプロジェクトに適しています。

  • bee-queue:

    Redis ベースのキューが必要だが、機能よりもシンプルさと軽量化を最優先する場合に選択します。大規模な機能セットは不要で、コードのフットプリントを小さく保ちたい軽量マイクロサービスなどに適しています。

  • bull:

    Redis を使用した堅牢なジョブキューが必要で、成熟したエコシステムと豊富な機能(優先度、遅延、イベントなど)を求めている場合に選択します。長年安定して運用されている実績を重視するプロジェクトに適しています。

  • bullmq:

    最新の Redis 機能(Streams)を活用し、高いパフォーマンスとモダンなアーキテクチャを求めている場合に選択します。bull の後継として設計されており、新規プロジェクトで Redis ベースのキューを採用する際の第一候補です。

  • kue:

    新規プロジェクトでの使用は推奨されません。メンテナンスが停止しており、セキュリティや互換性のリスクがあります。既存のレガシーシステムを維持する場合を除き、bullbullmq への移行を検討すべきです。

agenda のREADME

Agenda

Agenda

A light-weight job scheduling library for Node.js

NPM Version NPM Downloads

Migrating from v5? See the Migration Guide for all breaking changes.

Agenda 6.x

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.

Features

  • Minimal overhead job scheduling
  • Pluggable storage backends (MongoDB, PostgreSQL, Redis)
  • TypeScript support with full typing
  • Scheduling via cron or human-readable syntax
  • Configurable concurrency and locking
  • Real-time job notifications (optional)
  • Sandboxed worker execution via fork mode
  • TypeScript decorators for class-based job definitions

Installation

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:

  • Node.js 18+
  • Database of your choice (MongoDB 4+, PostgreSQL, or Redis)

Quick Start

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' });

Official Backend Packages

PackageBackendNotificationsInstall
@agendajs/mongo-backendMongoDBPolling onlynpm install @agendajs/mongo-backend
@agendajs/postgres-backendPostgreSQLLISTEN/NOTIFYnpm install @agendajs/postgres-backend
@agendajs/redis-backendRedisPub/Subnpm install @agendajs/redis-backend

Backend Capabilities

BackendStorageNotificationsNotes
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.
InMemoryNotificationChannelNotifications only. For single-process/testing.

Backend Configuration

MongoDB

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
});

PostgreSQL

import { Agenda } from 'agenda';
import { PostgresBackend } from '@agendajs/postgres-backend';

const agenda = new Agenda({
  backend: new PostgresBackend({
    connectionString: 'postgresql://user:pass@localhost:5432/mydb'
  })
});

Redis

import { Agenda } from 'agenda';
import { RedisBackend } from '@agendajs/redis-backend';

const agenda = new Agenda({
  backend: new RedisBackend({
    connectionString: 'redis://localhost:6379'
  })
});

Real-Time Notifications

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()
});

Mixing Storage and Notification Backends

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.

API Overview

Defining Jobs

// 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'
});

Defining Jobs with Decorators

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.

Scheduling Jobs

// 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

Job Control

// 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
  }
});

Stopping / Draining

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

Events

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) => { /* ... */ });

Use fail listeners to capture richer error context, such as stack traces, without storing large payloads in job.attrs.failReason:

agenda.on('fail', async (err, job) => {
	await saveJobError({
		jobId: job.attrs._id,
		jobName: job.attrs.name,
		message: err.message,
		stack: err.stack
	});
});

Custom Backend

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.

Documentation

Related Packages

Official Backend Packages:

Tools:

License

MIT