July 22, 2021 • ☕️ 7 min read
【 環境 】
Laravel のバージョン: 8.16.1
PHP のバージョン: 7.4.7
MySQL のバージョン: 5.7
Laravel の Schedule には、withoutOverlapping() というメソッドがあり、これを使わない限り、タスクが重複して実行される事があるみたい。
【 公式サイト 】
https://laravel.com/docs/8.x/scheduling#preventing-task-overlaps
By default, scheduled tasks will be run even if the previous instance of the task is still running.
To prevent this, you may use the withoutOverlapping method:
つまり、「1分に1回実行するバッチ」があり、そのバッチの実行完了に 100秒かかるとすると、バッチの終了を待たずに次々と実行される、という挙動だろうか。
というわけで、実験してみる。
1分に1回実行する、Batch01Command, Batch02Command, Batch03Command を設定。
protected function schedule(Schedule $schedule)
{
$this->scheduleExecuteBatches($schedule);
}
private function scheduleExecuteBatches(Schedule $schedule)
{
$this->scheduleExecuteCommand01($schedule);
$this->scheduleExecuteCommand02($schedule);
$this->scheduleExecuteCommand03($schedule);
}
private function scheduleExecuteCommand01(Schedule $schedule)
{
$schedule->command(Batch01Command::class)
->everyMinute()
->runInBackground();
}
private function scheduleExecuteCommand02(Schedule $schedule)
{
$schedule->command(Batch02Command::class)
->everyMinute()
->runInBackground();
}
private function scheduleExecuteCommand03(Schedule $schedule)
{
$schedule->command(Batch03Command::class)
->everyMinute()
->runInBackground();
}
開始・終了にログを吐く。
100秒のウェイトをかけて、処理に 1分以上かかるようにする。
public function handle()
{
$this->info(__METHOD__ . ':start');
\Log::info(__METHOD__. ':start');
// 100 秒間ウェイト
sleep(100);
$this->info(__METHOD__ . ':end');
\Log::info(__METHOD__. ':end');
}
// Batch02Command, Batch03Command も、全く同じ処理
そして、以下のコマンドで待ち受けを起動。
php artisan schedule:work
以下、ログに出力された内容です。
今回の検証に不要な部分は、見づらかったのでカットしました。
[2021-07-21 12:51:07] Batch02Command::handle:start
[2021-07-21 12:51:07] Batch01Command::handle:start
[2021-07-21 12:51:09] Batch03Command::handle:start
[2021-07-21 12:52:05] Batch01Command::handle:start
[2021-07-21 12:52:05] Batch02Command::handle:start
[2021-07-21 12:52:05] Batch03Command::handle:start
[2021-07-21 12:52:47] Batch02Command::handle:end
[2021-07-21 12:52:47] Batch01Command::handle:end
[2021-07-21 12:52:49] Batch03Command::handle:end
[2021-07-21 12:53:07] Batch02Command::handle:start
[2021-07-21 12:53:07] Batch01Command::handle:start
[2021-07-21 12:53:09] Batch03Command::handle:start
(中略)
[2021-07-21 12:55:45] Batch01Command::handle:end
[2021-07-21 12:55:45] Batch02Command::handle:end
[2021-07-21 12:55:45] Batch03Command::handle:end
[2021-07-21 12:56:05] Batch01Command::handle:start
[2021-07-21 12:56:05] Batch02Command::handle:start
[2021-07-21 12:56:06] Batch03Command::handle:start
[2021-07-21 12:56:48] Batch02Command::handle:end
[2021-07-21 12:56:48] Batch01Command::handle:end
[2021-07-21 12:56:48] Batch03Command::handle:end
[2021-07-21 12:56:56] Batch01Command::handle:end
[2021-07-21 12:56:56] Batch02Command::handle:end
[2021-07-21 12:56:56] Batch03Command::handle:end
Batch01Command, Batch02Command, Batch03Command は、それぞれのジョブの終了を待たず、同じジョブが同時に走っている。
業種や処理内容によっては、調査が非常に難しい障害が発生しそうなので、この挙動は抑えておいたほうがよさそう。
各バッチを「withoutOverlapping()」を付けて実行。
それ以外は「実験1」と全く同じ。
private function scheduleExecuteCommand01(Schedule $schedule)
{
$schedule->command(Batch01Command::class)
->everyMinute()
->runInBackground()
->withoutOverlapping();
}
private function scheduleExecuteCommand02(Schedule $schedule)
{
$schedule->command(Batch02Command::class)
->everyMinute()
->runInBackground()
->withoutOverlapping();
}
private function scheduleExecuteCommand03(Schedule $schedule)
{
$schedule->command(Batch03Command::class)
->everyMinute()
->runInBackground()
->withoutOverlapping();
}
以下、ログに出力された内容です。
[2021-07-21 13:09:07] Batch02Command::handle:start
[2021-07-21 13:09:07] Batch01Command::handle:start
[2021-07-21 13:09:09] Batch03Command::handle:start
[2021-07-21 13:10:47] Batch02Command::handle:end
[2021-07-21 13:10:47] Batch01Command::handle:end
[2021-07-21 13:10:49] Batch03Command::handle:end
[2021-07-21 13:11:08] Batch01Command::handle:start
[2021-07-21 13:11:08] Batch02Command::handle:start
[2021-07-21 13:11:10] Batch03Command::handle:start
[2021-07-21 13:12:48] Batch01Command::handle:end
[2021-07-21 13:12:48] Batch02Command::handle:end
[2021-07-21 13:12:50] Batch03Command::handle:end
[2021-07-21 13:13:08] Batch01Command::handle:start
[2021-07-21 13:13:08] Batch02Command::handle:start
[2021-07-21 13:13:09] Batch03Command::handle:start
[2021-07-21 13:14:48] Batch02Command::handle:end
[2021-07-21 13:14:48] Batch01Command::handle:end
[2021-07-21 13:14:49] Batch03Command::handle:end
さっきとは違い、重複して同じバッチが実行されないようになっている。
業務アプリなら、もう無条件で付けといてよいのではないだろうか。
余計な事故に発展しそうだし。
1分に1回動くスケジューラが動いているのですが、前回起動のジョブが完了しておらず、実行できるジョブが無かった場合、コンソールからはこんなメッセージが表示されました。
No scheduled commands are ready to run.
こんなのがありました。
【Laravel】 Cron タスクスケジューラの onOneServer() と withoutOverlapping() の違い
withoutOverlapping() は onOneServer() と併用して,初めて意味のある選択肢になります。
えええ!!! そうなの!?
と思いきや、
「重複起動しないように withoutOverlapping() を付けるんだろうけど、onOneServer() も付けないと他のサーバで重複実行される可能性があるから、併用しないと片手落ちになっちゃうよ。」
っていう意味か。
初見、
「withoutOverlapping() は、onOneServer() と一緒に使わないと意味をなさない(重複起動を防ぐ、という機能を満たさない)」
という意味かと思ってしまった。
(もちろん、そんな事は無い事は上記で検証済み)
という事で、バッチを実行するサーバが1台のみの場合、「withoutOverlapping()」だけでも、ジョブの重複起動を防止できます。
まぁ、そのうちサーバが増えた時に「onOneServer()」を追加するとか面倒なので、無条件で「onOneServer()」をくっつけといた方がいんじゃないかと思います。
理由としては、サーバ1台で動かす事を前提とした場合でも、特に何の問題も無いと確信が持てたからです。
調査内容が、こちら。
【Laravel】Schedule の onOneServer って、どんな動きをしているの? Laravel のソースコードを追ってみた。
Schedule にてジョブを起動している場合、重複して実行させたくない場合、
「->withoutOverlapping()」を付けよう!