Herokuで時間ごとにDynoの数を変更するスクリプト
経緯
テンマドでは「iruca」という業務向けの在席確認ツールを提供しています(大学等の教育機関でもご利用いただいています)。サービス開始当初からHerokuを利用しているのですが、便利に利用していた「Process Scheduler」というAdd-onが先日なくなってしまいました。
「Process Scheduler」で何を実現していたかというと、曜日・時間帯ごとにDyno(プロセスを実行する単位)の数を変更していました。業務向けのサービスなので、平日データイムの利用がほとんどです(出退勤の多い9時ごろと18時ごろが利用のピークだったりします)。夜間や週末・祝日の利用はあまり多くないので負荷も少なく、Dynoの数を調整することでコストを削減していました。
具体的には、普段はDynoを1つだけ動かし、平日データイムのみ2つ動かすようにしていたのです。
この先どうしようかな、と思ったところでHerokuのPlatform APIを利用すればよいことに気が付きました。もっと早く気が付けばよかった…!
ちなみに負荷の状況によってオートスケールさせるようなAdd-onがいくつかあるのですが、irucaでは単純に時間帯によって調整できるようにしたいので、今回は採用の対象外です。
調査
まずはPlatform APIのドキュメントを読んで、今回の目的が実現可能かどうか調べます。「Dyno Size」という項目があったのでそれかな…と思ったら違っていて、「Formation」(日本語にすると「構成」ですかね)という項目があって、「quantity」を操作すればできそうです。
それから、時間ごとにDynoの数を調整する方法を考えます。Herokuには「Heroku Scheduler」という機能があって、定期実行するコマンドを指定することができます。いわゆるバッチ処理向けの機能ですね。その機能を使って、1時間に一度、定期的にスクリプトを実行すればよさそうです。
また、直接APIをHTTP経由で操作するのは面倒です。irucaはNode.jsのアプリケーションなのですが、HerokuのAPI利用の手引きのドキュメントに、公式クライアントのnpmパッケージ「heroku-client」が紹介されていました。READMEを読んだ感じ、Promiseに対応していてそのまま使えそうです。そんなわけで、Node.jsのスクリプトを書くことにしました。
要件
要件をまとめます。
- 時間ごとにDynoの数を定義して、その数にDynoの数を変更できる
- 普段はDynoを1つだけ動かす
- 負荷が上がる期間は、2つ動かすようにする
- 変更するのは月曜日〜金曜日のみで、土曜日・日曜日は変更しなくてよい
- (できれば)祝日も土曜日・日曜日と同じ扱いとしたい
- 「Process Scheduler」では実現できていなかったが、この機会にできるようにしたい
実装
というわけで書いたコードです。平日の8時〜20時の間だけDynoの数を2つにして、それ以外は1つになるようにしてみました。Dynoは「Standard-1X」という種類を利用しています。
'use strict';
const Heroku = require('heroku-client');
const holidayJp = require('@holiday-jp/holiday_jp');
// Dynoの数、平日の時間による推移
const quantities = [
1, // 0時
1, // 1時
1, // 2時
1, // 3時
1, // 4時
1, // 5時
1, // 6時
1, // 7時
2, // 8時
2, // 9時
2, // 10時
2, // 11時
2, // 12時
2, // 13時
2, // 14時
2, // 15時
2, // 16時
2, // 17時
2, // 18時
2, // 19時
2, // 20時
1, // 21時
1, // 22時
1, // 23時
];
const size = 'Standard-1X';
const type = 'web';
(async () => {
const now = new Date();
const dayOfTheWeek = now.getDay();
const hour = now.getHours();
// Dynoの数、通常1つ
let quantity = 1;
if (
dayOfTheWeek !== 0 // 日曜日ではない
&& dayOfTheWeek !== 6 // 土曜日ではない
&& !holidayJp.isHoliday(now) // 祝日ではない
) {
// 平日にのみ変更する
quantity = quantities[hour];
}
const heroku = new Heroku({ token: '{your_heroku_api_token}' });
// https://devcenter.heroku.com/articles/platform-api-reference#formation
// Formation List API: 現在のDynoの数を取得
const formation = await heroku.get('/apps/{app_id_or_name}/formation');
const currentQuantity = formation[0].quantity;
if (currentQuantity !== quantity) {
// Formation Batch Update API: Dynoの数を変える
await heroku.patch(
'/apps/{app_id_or_name}/formation',
{ body: { updates: [{ quantity, size, type }] }}
);
console.log(`[Complete] Heroku Dyno: Scaled to ${type}@${quantity}:${size}`);
} else {
console.log('[Complete] Heroku Dyno: Kept the Dyno count');
}
})().catch((reason) => {
console.error(reason);
process.exit(1);
});
ちなみにこのHerokuのアプリケーションでは「Config Vars」(環境変数)で「TZ」に「Asia/Tokyo」を設定済みなので、Node.jsのDateクラスを使う場合、タイムゾーンとして日本時間が用いられるようになっています。
このコードをHerokuの環境にデプロイした後で、「Heroku Scheduler」で定期実行の設定をしました。毎時0分に、指定されたコードを実行します。
結果
その後、順調に祝日、土曜日、日曜日、平日と想定していた動作をしていることを確認しました。もっと早く書いておけばよかったなあ、というのが正直なところです。そうしたら「Process Scheduler」をそもそも利用する必要がなかったかも。
とは言え、今まですごく「Process Scheduler」にはお世話になったので、ありがとうという気持ちです。どうして終了してしまったのかは謎のままだったりするのですが(Herokuから急に存在が消えてしまったので、正直困惑しています…)。
しばらくはこのスクリプトを使い続けることになりそうです。
合同会社テンマドは、Web制作・システム開発だけでなくいろいろなことを企んでいく会社です。事業として「クライアントワーク」「ミソラプロジェクト」「オウンドサービス」のそれぞれに取り組んでいます。会社としてのミッションは「あかるい未来をかたちにする」です。 10mado.jp/
Discussion