- はじめに
- Schedule Task とは
- Schedule Task を使う事による利点
- Schedule Task のつらみ
- Laravel と一緒に使う時のデザインパターン
- Schedule task からの呼び出し
- 多重起動対策
- その他
はじめに
DROBE では cron として実行したい Application にまつわる処理を ECS の Schedule Task を利用して運用しています
この記事では Fargate Schedule Task を Laravel でどう使っているかや管理方法などを記載します
Schedule Task とは
ECS の Schedule task は、cron のようなスケジュールされた Task を起動してくれる機能です
DROBE では Cloud Watch の Event をトリガーとして、Fargate の Task を起動しています
Schedule Task を使う事による利点
Schedule Task を利用して Fargate を cront として活用する最大の利点は cpu や memory などのリソースが task 毎に完全に独立となるので、原理的に task 同士が干渉しずらくなる事だと考えています。例えばプログラムのミスでどこかの task が大量に memory を使ってしまったとしても、それが原因で他のタスクに影響が出るという事はほぼありません
また Fargate は 1 秒辺りで課金されるので、cron 用に数台サーバーを常時持っていくよりも安くなる可能性もあります
Schedule Task のつらみ
一見いい事ずくめの Schedule task ですが、一つ大きなつらみがあります。それは task の多重起動問題です
これは公式でも言及されており、残念ながら暫くは仕様として飲み込むしかなさそうだと考えています
まれに、単一のイベントまたはスケジュールされた期間に対して同じルールを複数回トリガーしたり、特定のトリガーされたルールに対して同じターゲットを複数回起動したりする場合があります。
多重起動をインフラレベルで抑えるすべが無いとすると、アプリケーションレイヤーで対策を行うしかありません。DROBE に於いては以下のような対策を取っています
- Task で実行される処理は冪等性が担保されるように書く
- 多重起動されても大丈夫なように Task の処理にロックを取る
1 はそもそも実行が失敗するなどもあり得るので頑張ってやっていこうという話だと考えています
この記事では以降 2 の Task 処理のロックについて書いていきます
Laravel と一緒に使う時のデザインパターン
ここから Laravel と Schedule task を使う場合のデザインについて書いていきます
Schedule task からの呼び出し
まず Schedule task から任意の処理をどうやって呼び出すかですが、DROBE では Laravel の Artisan command を自作して、それを schedule task から叩いてもらうようにしています
Artisan command はオプションや引数も受け取れるので、必要に応じて schedule task からそれらを受け取れるように汎用的に作る事も可能です
多重起動対策
多重起動対策としては redis の atomic lock 機能を利用しています
atomic lock を使う事で分散環境においてもレースコンディションを心配する事なくロックを取る事が可能です
具体的には以下のようなコードでロックを取っています
// lock を取る処理
public static function runIfNotRunning(string $cacheKey, callable $func, int $lockSec = 120)
{
$lockResult = Cache::lock($cacheKey, $lockSec);
if ($lockResult['locked']) {
$func(); // ここで処理を実行する
} else {
Log::info("{$cacheKey} is already running. do nothing.");
}
}
// 使う側
self::runIfNotRunning("key", function() {
// task 内で行いたい処理
});
$lockSec
は task が終わるまでの時間によって増減させるのが良いと思います
その他
Schedule task の多重起動は、経験上最初の起動の 1 ~ 2 分後に次のタスクが起動してしまうといった事もありました
その場合は、例えば task の処理がすぐに終わってしまったり、次のタスクの起動前に $lockSec
以上の時間がたってしまったりすると 2 つめの task の実行が始まってしまいます
これに関してはあまり良い対策が思いついていないというのが正直な所で、 $lockSec
の調整とアプケーション側の冪等性に頼るという辛みを抱えた運用になっています