agenda、bull、node-schedule は、Node.js アプリケーションで時間に基づいたタスクやバックグラウンド処理を実行するためのライブラリです。それぞれデータの保存方法と設計思想が異なり、agenda は MongoDB を使用し柔軟なスケジューリングを提供します。bull は Redis をベースにした高速なキューシステムであり、node-schedule はプロセス内で動作するシンプルな cron 風スケジューラーです。
Node.js でバックグラウンドタスクや定期処理を実装する際、agenda、bull、node-schedule は代表的な選択肢です。これらはどれも「特定の時間にコードを実行する」という目的を果たしますが、内部の仕組みと適した場面が全く異なります。ここでは、実務的な観点からそれぞれの違いを深掘りします。
ジョブ情報をどこに保存するかは、システムの信頼性に直結します。
agenda は MongoDB にジョブ情報を保存します。
// agenda: MongoDB 接続が必要
const agenda = new Agenda({ db: { address: 'mongodb://localhost:27017/agenda' } });
await agenda.start();
bull は Redis にジョブ情報を保存します。
// bull: Redis 接続が必要
const Queue = require('bull');
const jobQueue = new Queue('email jobs', 'redis://127.0.0.1:6379');
node-schedule はメモリー上にジョブ情報を保持します。
// node-schedule: DB 不要
const schedule = require('node-schedule');
// 接続設定は不要。そのまま使用可能
タスクを実行するタイミングをどう指定するかは、ライブラリごとにsyntax が違います。
agenda は日付オブジェクトまたは cron 式を使えます。
// agenda: 日付または cron
agenda.schedule('in 2 days', 'send email', { userId: 123 });
agenda.every('3 minutes', 'calculate stats');
bull は主にキューへの追加タイミングを制御します。
// bull: delay または cron
jobQueue.add({ id: 1 }, { delay: 5000 }); // 5 秒後
jobQueue.add({ id: 2 }, { repeat: { cron: '*/5 * * * *' } }); // 5 分ごと
node-schedule は cron 式または Date オブジェクトに特化しています。
// node-schedule: cron または Date
schedule.scheduleJob('*/5 * * * *', () => { console.log('run'); });
schedule.scheduleJob(new Date(2024, 11, 25), () => { console.log('Xmas'); });
複数のサーバーで同じアプリを動かす場合、ジョブが重複実行されないようにする必要があります。
agenda はデータベースレベルでロックをかけます。
// agenda: ロック処理は内部で自動管理
agenda.define('send report', async (job) => {
// 重複実行防止のロックが自動でかかる
await sendReport(job.attrs.data);
});
bull も Redis 上でロックを管理します。
// bull: 処理中のロック管理
jobQueue.process(async (job) => {
// 処理中にプロセスが落ちると、ジョブは失敗としてマークされる
await processJob(job.data);
});
node-schedule はロック機能を持ちません。
// node-schedule: ロックなし
schedule.scheduleJob('0 0 * * *', () => {
// 複数サーバーだと全員同時に実行してしまう
cleanupDatabase();
});
| 特徴 | agenda | bull | node-schedule |
|---|---|---|---|
| 保存先 | MongoDB | Redis | メモリー |
| 永続性 | ✅ あり | ✅ あり | ❌ なし |
| 分散対応 | ✅ 可能 | ✅ 可能 | ❌ 不可 |
| 設定の手間 | 中 (DB 必要) | 中 (Redis 必要) | 小 (不要) |
| 主な用途 | 複雑なスケジュール | 高速キュー処理 | 簡易定期処理 |
node-schedule は、開発環境や、cron 処理が失敗しても次回の起動で問題ない単純なタスク — 例えば、キャッシュの削除やログのローテーション — に最適です。設定が簡単で、すぐに使えます。
bull は、メール送信や画像処理など、失敗したらリトライしたいタスクや、順序制御が必要なキュー処理に向いています。Redis 環境があるなら、パフォーマンスと機能のバランスが最も良い選択です。
agenda は、ユーザーごとの個別スケジュール — 例えば「ユーザー A は 3 日後、ユーザー B は 5 日後に通知」 — を管理したい場合に真価を発揮します。MongoDB を既に使っているプロジェクトでは、自然な選択になります。
どれを選ぶにしても、重要なのは「サーバーが再起動したときに、そのジョブはどうなるか」を最初に決めることです。そこを間違えると、後で大きな手戻りが発生します。
node-schedule を選ぶべきなのは、サーバー再起動でジョブ情報が消えても困らない場合です。インメモリで動作するため設定が簡単で、単一プロセスのアプリケーションや、開発環境での簡易スケジューリングに最適です。分散環境や永続性が必要な本番環境には向きません。
bull を選ぶべきなのは、Redis を利用可能で、大量のジョブを高速に処理したい場合です。キューの優先順位付けや、ジョブの失敗時のリトライ制御が細かくできる点が強みです。なお、後継の bullmq も存在しますが、bull は依然として安定しており広く使われています。
agenda を選ぶべきなのは、既に MongoDB を使用しており、ジョブの履歴や状態を永続化したい場合です。複雑な繰り返しスケジュールや、ジョブのロック機能が必要なシーンに適しています。ただし、MongoDB の接続維持が必要になるため、インフラ構成が少し重くなります。
Node Schedule is a flexible cron-like and not-cron-like job scheduler for Node.js. It allows you to schedule jobs (arbitrary functions) for execution at specific dates, with optional recurrence rules. It only uses a single timer at any given time (rather than reevaluating upcoming jobs every second/minute).
Node 6+ is supported.
Node Schedule is for time-based scheduling, not interval-based scheduling.
While you can easily bend it to your will, if you only want to do something like
"run this function every 5 minutes", toad-scheduler would be a better choice. But if you want to, say, "run this function at the :20
and :50 of every hour on the third Tuesday of every month," you'll find that
Node Schedule suits your needs better. Additionally, Node Schedule has Windows
support, unlike true cron, since the node runtime is now fully supported.
Note that Node Schedule is designed for in-process scheduling, i.e. scheduled jobs will only fire as long as your script is running, and the schedule will disappear when execution completes. If you need to schedule jobs that will persist even when your script isn't running, consider using actual cron.
In case you need durable jobs that persist across restarts and lock system compatible with multi-node deployments, try agenda or bree.
You can install using npm.
npm install node-schedule
Every scheduled job in Node Schedule is represented by a Job object. You can
create jobs manually, then execute the schedule() method to apply a schedule,
or use the convenience function scheduleJob() as demonstrated below.
Job objects are EventEmitters, and emit the following events:
run event after each execution.scheduled event each time they're scheduled to run.canceled event when an invocation is canceled before it's executed.canceled is the single-L American spelling.error event when a job invocation triggered by a schedule throws or returns
a rejected Promise.success event when a job invocation triggered by a schedule returns successfully or
returns a resolved Promise. In any case, the success event receives the value returned
by the callback or in case of a promise, the resolved value.(Both the scheduled and canceled events receive a JavaScript date object as
a parameter).
Note that jobs are scheduled the first time immediately, so if you create a job
using the scheduleJob() convenience method, you'll miss the first scheduled
event, but you can query the invocation manually (see below).
The cron format consists of:
* * * * * *
┬ ┬ ┬ ┬ ┬ ┬
│ │ │ │ │ │
│ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun)
│ │ │ │ └───── month (1 - 12)
│ │ │ └────────── day of month (1 - 31)
│ │ └─────────────── hour (0 - 23)
│ └──────────────────── minute (0 - 59)
└───────────────────────── second (0 - 59, OPTIONAL)
Examples with the cron format:
const schedule = require('node-schedule');
const job = schedule.scheduleJob('42 * * * *', function(){
console.log('The answer to life, the universe, and everything!');
});
Execute a cron job when the minute is 42 (e.g. 19:42, 20:42, etc.).
And:
const job = schedule.scheduleJob('0 17 ? * 0,4-6', function(){
console.log('Today is recognized by Rebecca Black!');
});
Execute a cron job every 5 Minutes = */5 * * * *
You can also get when it is scheduled to run for every invocation of the job:
const job = schedule.scheduleJob('0 1 * * *', function(fireDate){
console.log('This job was supposed to run at ' + fireDate + ', but actually ran at ' + new Date());
});
This is useful when you need to check if there is a delay of the job invocation when the system is busy, or save a record of all invocations of a job for audit purpose.
Currently, W (nearest weekday) and L (last day of month/week) are not supported.
Most other features supported by popular cron implementations should work just fine,
including # (nth weekday of the month).
cron-parser is used to parse crontab instructions.
Say you very specifically want a function to execute at 5:30am on December 21, 2012. Remember - in JavaScript - 0 - January, 11 - December.
const schedule = require('node-schedule');
const date = new Date(2012, 11, 21, 5, 30, 0);
const job = schedule.scheduleJob(date, function(){
console.log('The world is going to end today.');
});
To use current data in the future you can use binding:
const schedule = require('node-schedule');
const date = new Date(2012, 11, 21, 5, 30, 0);
const x = 'Tada!';
const job = schedule.scheduleJob(date, function(y){
console.log(y);
}.bind(null,x));
x = 'Changing Data';
This will log 'Tada!' when the scheduled Job runs, rather than 'Changing Data', which x changes to immediately after scheduling.
You can build recurrence rules to specify when a job should recur. For instance, consider this rule, which executes the function every hour at 42 minutes after the hour:
const schedule = require('node-schedule');
const rule = new schedule.RecurrenceRule();
rule.minute = 42;
const job = schedule.scheduleJob(rule, function(){
console.log('The answer to life, the universe, and everything!');
});
You can also use arrays to specify a list of acceptable values, and the Range
object to specify a range of start and end values, with an optional step parameter.
For instance, this will print a message on Thursday, Friday, Saturday, and Sunday at 5pm:
const rule = new schedule.RecurrenceRule();
rule.dayOfWeek = [0, new schedule.Range(4, 6)];
rule.hour = 17;
rule.minute = 0;
const job = schedule.scheduleJob(rule, function(){
console.log('Today is recognized by Rebecca Black!');
});
Timezones are also supported. Here is an example of executing at the start of every day in the UTC timezone.
const rule = new schedule.RecurrenceRule();
rule.hour = 0;
rule.minute = 0;
rule.tz = 'Etc/UTC';
const job = schedule.scheduleJob(rule, function(){
console.log('A new day has begun in the UTC timezone!');
});
A list of acceptable tz (timezone) values can be found at https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
second (0-59)minute (0-59)hour (0-23)date (1-31)month (0-11)yeardayOfWeek (0-6) Starting with SundaytzNote: It's worth noting that the default value of a component of a recurrence rule is
null(except for second, which is 0 for familiarity with cron). If we did not explicitly setminuteto 0 above, the message would have instead been logged at 5:00pm, 5:01pm, 5:02pm, ..., 5:59pm. Probably not what you want.
To make things a little easier, an object literal syntax is also supported, like in this example which will log a message every Sunday at 2:30pm:
const job = schedule.scheduleJob({hour: 14, minute: 30, dayOfWeek: 0}, function(){
console.log('Time for tea!');
});
It will run after 5 seconds and stop after 10 seconds in this example. The ruledat supports the above.
const startTime = new Date(Date.now() + 5000);
const endTime = new Date(startTime.getTime() + 5000);
const job = schedule.scheduleJob({ start: startTime, end: endTime, rule: '*/1 * * * * *' }, function(){
console.log('Time for tea!');
});
You can shutdown jobs gracefully.
gracefulShutdown() will cancel all jobs and return Promise.
It will wait until all jobs are terminated.
schedule.gracefulShutdown();
You can also gracefully shutdown jobs when a system interrupt occurs.
process.on('SIGINT', function () {
schedule.gracefulShutdown()
.then(() => process.exit(0))
}
There are some function to get information for a Job and to handle the Job and Invocations.
You can invalidate any job with the cancel() method:
j.cancel();
All planned invocations will be canceled. When you set the parameter reschedule to true then the Job is newly scheduled afterwards.
This method invalidates the next planned invocation or the job. When you set the parameter reschedule to true then the Job is newly scheduled afterwards.
This method cancels all pending invocation and registers the Job completely new again using the given specification. Return true/false on success/failure.
This method returns a Date object for the planned next Invocation for this Job. If no invocation is planned the method returns null.
This module was originally developed by Matt Patenaude who eventually passed over maintainer's mantle over to Tejas Manohar.
Currently it is being maintained by Igor Savin and our amazing community.
We'd love to get your contributions. Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit.
Before jumping in, check out our Contributing page guide!
Copyright 2015 Matt Patenaude.
Licensed under the MIT License.