🐙

esa2githubの予約投稿機能で使うスケジューラーをAgenda(MongoDB)からBull(Redis)に移行したときにやったこと

2020/11/13に公開

esa2githubとは

esa2github は、esaのwebhookを受け取ってGitHubにpushできるアプリケーションです。

  • 記事ごとにGitHubにpushする日時を指定できる(予約投稿機能)
  • GitHubにpushする際に記事冒頭に任意のfrontmatterを付加することができる

という点が esa標準のGitHub Webhook との違いです。

詳細は

esaの記事をGitHubにpushする高機能webhookを作った(予約投稿&frontmatter自由記述)

こちらの過去記事をご参照ください。

予約投稿機能の実装にAgendaを使っていたけど、Bullを使った実装に変更した

esa2githubは、無料&ノーコードでHerokuにデプロイして利用できるように作ってあります。

Deploy to 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の agenda.schedule() に相当する機能がBullにはなかった

今回のコードの変更内容は こんな感じ です。

使うライブラリを差し替えただけですが、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 もぜひ使ってみてくださいね🙌

GitHubで編集を提案

Discussion