Laravel 13.31では、中断可能なジョブ(interruptible jobs)が追加されました。これにより、キューワーカーがデプロイ中に送信されるSIGTERMシグナルをキャッチし、タスクを途中で中断させるのではなく、クリーンにシャットダウンできるようになります。数万行の処理、大量のメール送信、レポート作成など、長時間のジョブを実行するチームにとって、新しいリリースが展開される際にもデータの整合性が保たれるというメリットがあります。
なぜデプロイがジョブを強制終了させてしまうのか
新しいバージョンが導入される際、Supervisor、Docker、Kubernetesなどのプロセス管理ツールは、既存のワーカーに対して終了を求める丁寧なリクエストであるSIGTERMを送信し、停止を指示します。ほとんどのワーカーは、現在のジョブが完了してから終了するのを待ちます。問題は、管理ツールが遵守のために与える時間がわずか数秒しかない場合に発生します。ジョブに数分かかる場合、管理ツールはSIGKILLへとエスカレートし、プロセスを即座に強制終了させます。その結果、ジョブは一貫した状態に到達できず、一部だけ更新されたレコード、孤立したファイル、あるいは重複した作業が残ることになります。
Laravelの回答:協調的な中断
Laravel 13.31では、ジョブクラスが終了リクエストを認識できるようにするための Interruptible という名前のコントラクト(contract)が導入されました。SIGTERMが届くと、Laravelはジョブの interrupted() メソッドを呼び出します。フレームワークはジョブを自動的に停止させるわけではありません。ジョブ側でLaravelが設定するフラグを確認し、自ら終了する必要があります。この協調モデルにより、開発者は現在のループの反復を完了させたり、部分的な変更をロールバックしたり、終了前にチェックポイントを書き込んだりすることが可能になります。
ジョブを中断可能にする方法
- コントラクトを実装する – ジョブクラスの定義に
implements Interruptibleを追加します。 - ワークループ内でフラグを確認する – 各反復(iteration)ごとに、ジョブを停止すべきかどうかを確認するチェックを挿入します。
- クリーンアップを実行する –
interrupted()内で、後でジョブを再開できるように状態を保存するか、後で分析するために中断をログに記録します。
最大の罠:--once を使わないこと
php artisan queue:work --once を使用してキューワーカーを実行すると、シグナルハンドリングが完全に無効になります。ワーカーは単一のジョブを処理して終了しますが、SIGTERMハンドラーを一切登録しません。その結果、中断可能なジョブであっても終了リクエストを無視し、処理の途中で強制終了されてしまいます。このパターンは、cronで駆動するコンテナやワンショットのデプロイメントで見られます。中断可能なジョブが必要な場合は、常にデーモンモード (php artisan queue:work) で実行することで解決してください。
中断の監視
Laravelは、フック可能な2つのイベントを発行します:
- WorkerInterrupted – いかなるワーカーがSIGTERMを受信したときにも発行されます。このイベントをログに記録することで、デプロイによってワーカーがどの程度の頻度で中断されているかという全体像を把握できます。
- JobInterrupted – 現在のジョブが
Interruptibleコントラクトを実装している場合にのみ発行されます。一時ファイルの削除や「最後に処理された行」のマーカーの更新など、ジョブ固有のクリーンアップを実行するためにこのイベントを使用します。
これらのイベントをリスニングすることで、チームはデプロイの影響を示すダッシュボードを構築したり、頻繁に中断されているジョブを特定したりできるようになります。
キューサイズのレポート機能が改善
このリリースでは、キューマネージャーに totalSize() メソッドが追加され、すべてのキューに保留されているジョブの総数を返します。この値は、実際のカウントを報告するデータベース駆動およびRedisドライバーにおいて信頼できます。SQS、Sync、Beanstalkdなどのドライバーは、LaravelのAPIを通じてカウントを公開していないため、引き続きゼロを返します。SQSを使用しているチームは、キューの深さについては引き続きCloudWatchメトリクスに依存する必要があります。
さまざまなセットアップにおける意味
- Docker/Kubernetes – グレースフルシャットダウン期間を効果的に活用できるようになります。ジョブがフラグに気づいてクリーンに終了するための数秒間を確保できるよう、終了猶予期間(termination grace period)を延長してください。
- Supervisor – 中断フラグを受け取った後、ジョブが完了すると予想される時間よりも長い値を
stopwaitsecsに設定してください。 - レガシーなジョブ – 管理ツールのタイムアウトよりも長く実行されるジョブをレビューし、可能な場合は中断可能にしてください。
留意点:複雑さの増加
この機能は、適切なタイムアウト設定に代わるものではありません。ジョブのループが中断フラグを一度も確認しない場合、管理ツールは依然としてSIGKILLを送信します。チームは、実行時間の長いコードパスを監査し、論理的なブレークポイントにチェックを挿入する必要があります。また、シャットダウンの猶予時間内にすでに終了するような短いジョブに対して、コントラクトの実装やフラグのチェックといった追加のボイラープレートを導入することに、抵抗を感じる開発者もいるかもしれません。
アクションチェックリスト
- デプロイスクリプトをスキャンして
--onceフラグを探し、中断可能なジョブを使用するデーモンモードに置き換えます。 - 実行時間の長いジョブ(数万行のデータに触れるものや、数分間実行されるもの)を特定し、
Interruptibleコントラクトを追加します。 - 各ループのイテレーション内にフラグのチェックを挿入し、必要な終了処理コードを
interrupted()メソッドに移動します。 WorkerInterruptedとJobInterruptedにリスナーをフックして、デプロイが処理にどの程度の頻度で影響を与えるかについてのメトリクスを収集します。- Redis またはデータベースキューの場合は
totalSize()を使用してバックログを監視し、SQS の場合は CloudWatch アラームをそのまま維持します。
デプロイに伴うシャットダウンを、突然の強制終了ではなく、調整されたプロセスとして扱います。Laravel 13.31 はデータの整合性を維持し、未完了のジョブによる運用上の負担を軽減します。トレードオフとして、コードの実装負荷はわずかに増加しますが、大量のバックグラウンド処理に依存しているチームは、より信頼性の高い本番環境を手に入れることができます。
