esa2githubの予約投稿機能で使うスケジューラーをAgenda(MongoDB)からBull(Redis)に移行したときにやったこと
esa2githubとは
esa2github は、esaのwebhookを受け取ってGitHubにpushできるアプリケーションです。
- 記事ごとにGitHubにpushする日時を指定できる(予約投稿機能)
- GitHubにpushする際に記事冒頭に任意のfrontmatterを付加することができる
という点が esa標準のGitHub Webhook との違いです。
詳細は
esaの記事をGitHubにpushする高機能webhookを作った(予約投稿&frontmatter自由記述)
こちらの過去記事をご参照ください。
予約投稿機能の実装にAgendaを使っていたけど、Bullを使った実装に変更した
esa2githubは、無料&ノーコードでHerokuにデプロイして利用できるように作ってあります。
このボタンでHerokuにデプロイできます。README を参考にして実際に使ってみてください👍
予約投稿機能の実装にはもともと Agenda を使っていて、バックエンドのMongoDBサーバーにはHerokuのmLab MongoDBアドオンを使うようにしていました。
が、HerokuでMongoDBを無料で使える唯一のアドオンだったmLab MongoDBアドオンが2020/11/10をもってシャットダウンしてしまったので、Bull + Redisを使った実装に変更しました。
実装を変更せずに MongoDB Atlas を使うという選択肢も考えましたが、今までどおり
Deploy to Heroku
ボタンポチーでデプロイ完了できるようにしておきたかったので、Redisなら Heroku Redis が使えるということで そうしました。
agenda.schedule()
に相当する機能がBullにはなかった
Agendaの 今回のコードの変更内容は こんな感じ です。
使うライブラリを差し替えただけですが、AgendaとBullで機能面に差があってちょっとだけ苦労しました。
もともとの実装では、
Webプロセス側
await agenda.start()
await agenda.schedule({実行させたい日時}, 'ジョブ名', {ジョブに渡したいデータ})
Workerプロセス側
agenda.define('ジョブ名', async job => {
const {owner, repo, branch, path, message, content} = job.attrs.data
// ここに実際の処理内容
})
(async () => {
await agenda.start()
await agenda.every('1 minutes', 'ジョブ名')
})()
という感じで、Agendaの schedule メソッドを使って直感的に日時指定ができていました。
が、Bullには残念ながらこれに相当する機能がなく、cron形式で繰り返しを定義できる のみでした。
なので、以下のように queue.process()
内で setInterval()
を使って1分ごとに処理を繰り返して、現在日時が指定日時を過ぎていたら実行する、という実装を書きました。
Webプロセス側
await queue.add({
// ジョブに渡したいデータ
// ...
executeAfter: {実行させたい日時},
})
Workerプロセス側
let timer
const processor = async (job, done) => {
const {渡された各種データ..., executeAfter} = job.data
if (dayjs().isAfter(executeAfter)) {
// ここに実際の処理内容
done()
clearInterval(timer)
}
}
// process every minute
queue.process((job, done) => {
timer = setInterval(() => processor(job, done), 1000 * 60)
})
ジョブを done()
するだけでなく、ちゃんと clearInterval()
しないと、指定日時以降ずっとジョブが1分ごとに繰り返し実行されてしまうので要注意です。
おまけ:Herokuを使わないならBullよりもBreeやmicrojobがよさそう
今回はHerokuを使う前提だったので、WebとWorkerが別プロセスになるため、Redisなどのデータベースを使ってジョブをプロセス間で共有する必要がありましたが、もしWebとWorkerを同一プロセスでホストできる環境であるなら、ざわざわRedisなどに依存しなくても、Node.jsのWorker Threads を使うのがシンプルでよさそうだなと思いました。
実装としては
などが有力な選択肢だと思います。
特に Bree はAgendaの元メンテナーの方が作っていて使い勝手もよさそうなので、使う機会があれば試してみたいなと思っています。
README#Foreward に作者の熱い思いが書かれています。
まとめ
自分で作業していて、Web上にピンポイントで欲しい情報がなかなか見られなかった(日本語の情報に至ってはほぼ皆無)ので、備忘録と共有のために残しておきます。
誰かのお役に立てば嬉しいです。
esa2github もぜひ使ってみてくださいね🙌
Discussion