Laravel『Queue Timeout』の原因と対処法
- 作成日 2026.01.28
- その他
Laravelの「Queue Timeout」は、キューワーカーがジョブ実行中にタイムアウト上限へ到達し、ワーカー側またはプロセスマネージャ(Supervisor/systemd)側によりジョブが強制終了される状態を指す。典型的には「TimeoutExceededException」「ジョブが途中で落ちて再試行が繰り返される」「失敗ジョブに入り続ける」「同じ処理が二重実行される」などの形で表面化する。原因は、処理時間そのものが長い(外部API/重いDB/大容量ファイル)か、タイムアウト設定(queue worker / supervisor / horizon / PHP)が噛み合っていないか、メモリ枯渇・デッドロック等で“進まないまま時間だけ経つ”かのどれかに収束する。
- 1. 症状と発生条件(典型例)
- 2. まず切り分け:どのタイムアウトで落ちているか(層の特定)
- 3. 原因1:ジョブ処理が単純に長すぎる(設計の問題)
- 4. 原因2:ワーカーの –timeout が短い(queue:work)
- 5. 原因3:Supervisor/systemd の停止猶予が短く、再起動でkillされる
- 6. 原因4:Horizon の timeout が短い(Redisキュー)
- 7. 原因5:外部API/HTTP待ちが長い、もしくは無限待ち
- 8. 原因6:DBロック/デッドロック/長時間トランザクションで進まない
- 9. 原因7:メモリ枯渇→スワップ多発→タイムアウトに見える
- 10. サンプル:重い処理を「分割 + 専用キュー + timeout指定」で安定化
- 11. チェックリスト(上から順に確認する)
症状と発生条件(典型例)
よくあるメッセージ例。
Illuminate\Queue\TimeoutExceededException
The job has been attempted too many times.
Process exceeded the timeout of 60 seconds.
Killed発生条件の典型:
・キューで画像変換、PDF生成、動画処理、CSV取込など重い処理を実行
・外部API呼び出しでレスポンス待ちが長い/タイムアウトが無い
・大量のDB更新/集計を1ジョブに詰め込んでいる
・デッドロック/ロック待ちで処理が止まる
・Supervisorのstopwaitsecsよりジョブが長く、再起動で途中killされる
・Horizonでtimeout設定が短い
・PHPの max_execution_time / FPM request_terminate_timeout が短い(環境による)
まず切り分け:どのタイムアウトで落ちているか(層の特定)
タイムアウトは複数箇所に存在し、最短のものが勝つ。主な層:
・Laravelワーカー(php artisan queue:work の –timeout)
・Horizon(config/horizon.php の timeout)
・Supervisor/systemd(stopwaitsecs / TimeoutStopSec)
・PHP(CLIなら基本無制限に近いが、環境やラッパにより制限あり)
・外部API/HTTPクライアント(接続・応答タイムアウト)
確認の第一歩:failed_jobs の exception を読み、どの層の文言か見分ける。
原因1:ジョブ処理が単純に長すぎる(設計の問題)
発生条件:
・1ジョブで全件処理(数万レコード更新、全画像生成、全メール送信など)
・ループ中に外部APIを直列で叩いている
対処:ジョブを分割し、1ジョブあたりの上限時間を短くする。
例:CSVを行単位/チャンク単位に分割してディスパッチする。
use Illuminate\Support\Facades\Bus;
$jobs = collect($rows)->chunk(500)->map(function ($chunk) {
return new ImportChunkJob($chunk->all());
});
Bus::batch($jobs)->dispatch();「分割 + 冪等(同じジョブが複数回走っても壊れない)」がタイムアウト対策の基本形。
原因2:ワーカーの –timeout が短い(queue:work)
発生条件:
・デフォルト/運用値が60秒など短く、実処理が超える
対処:queue:work の timeout を延ばす。
php artisan queue:work --timeout=300 --tries=3ジョブ側で timeout を指定する運用もある(ジョブごとに上限を変える)。
class HeavyJob implements ShouldQueue
{
public $timeout = 300;
public $tries = 3;
public function handle()
{
// ...
}
}注意点:timeoutを上げるだけだと、詰まり(ロック待ち、無限待ち)も延命してしまうため、処理分割とセットにする。
原因3:Supervisor/systemd の停止猶予が短く、再起動でkillされる
発生条件:
・デプロイやプロセス再起動(queue:restart / supervisor restart)時に、stopwaitsecs が短い
・ジョブが実行中なのにワーカーが強制停止される
対処:Supervisorの stopwaitsecs をジョブ最大実行時間より長くする。例(概念)。
; supervisor設定例(概念)
stopwaitsecs=600systemdなら TimeoutStopSec を確認する(概念)。
# systemd(概念)
TimeoutStopSec=600「Laravel側timeoutは長いのに、プロセスマネージャ側が先にkillする」ケースが多い。
原因4:Horizon の timeout が短い(Redisキュー)
発生条件:
・Horizon運用で、config/horizon.php の timeout が短い
・長いジョブが監視ポリシーで落とされる
対処:Horizonの supervisor 設定で timeout を調整する(概念)。
// config/horizon.php(概念)
'environments' => [
'production' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['default'],
'timeout' => 300,
'tries' => 3,
],
],
],Horizonはワーカー数やバランスも含めて詰まりやすいので、重いジョブ専用キューを分けるのが有効。
原因5:外部API/HTTP待ちが長い、もしくは無限待ち
発生条件:
・外部サービスが遅い/落ちている
・HTTPクライアントに timeout 設定がない
対処:通信は必ず接続・応答のタイムアウトを設け、失敗時はリトライ戦略を分ける。
use Illuminate\Support\Facades\Http;
$response = Http::timeout(10)
->connectTimeout(3)
->retry(2, 200)
->get($url);
$response->throw();外部API待ちを1ジョブで大量に直列処理すると簡単にtimeoutへ到達するため、分割・並列化・バルクAPI化の検討も必要。
原因6:DBロック/デッドロック/長時間トランザクションで進まない
発生条件:
・SELECT … FOR UPDATE を長時間保持
・大きいトランザクションで更新し続ける
・他処理と競合しロック待ちが発生
対処:
・トランザクション範囲を最小化
・更新対象をチャンク化
・ロック順序を揃える
・DB側のロック待ちタイムアウトやログを確認して根を潰す
更新を分割する例。
DB::transaction(function () use ($ids) {
Model::whereIn('id', $ids)->update(['status' => 'done']);
}, 3);“止まっている時間”が長いと、処理量が少なくてもtimeoutする。
原因7:メモリ枯渇→スワップ多発→タイムアウトに見える
発生条件:
・大配列/巨大レスポンス/画像処理でメモリを食い、極端に遅くなる
・OOMでkillされ、結果的にtimeoutっぽく見える
対処:
・chunk/cursorで逐次処理
・不要なリレーション eager load を避ける
・大きいファイルはストリーム処理
・ワーカーのメモリ上限(–memory)も適切に設定
php artisan queue:work --memory=256 --timeout=300サンプル:重い処理を「分割 + 専用キュー + timeout指定」で安定化
例:画像変換を1枚ずつジョブ化し、長い処理は専用キューへ。
// dispatch側
foreach ($imageIds as $id) {
ConvertImageJob::dispatch($id)->onQueue('heavy');
}
// job側
class ConvertImageJob implements ShouldQueue
{
public $timeout = 180;
public $tries = 3;
public function __construct(public int $imageId) {}
public function handle()
{
$image = Image::findOrFail($this->imageId);
// 画像処理、S3アップロード等
}
}専用キューを別ワーカーで回す(概念)。
php artisan queue:work --queue=heavy --timeout=300 --tries=3
php artisan queue:work --queue=default --timeout=60 --tries=3
チェックリスト(上から順に確認する)
1) failed_jobs の例外は TimeoutExceededException か?それともSupervisor/systemdのkillログか?
2) そのジョブの平均実行時間は何秒か(ログで start/end を出して把握)
3) 1ジョブに詰め込みすぎていないか(分割・チャンク化できるか)
4) queue:work の –timeout と tries は実処理に合っているか
5) Supervisor/systemd の停止猶予(stopwaitsecs/TimeoutStopSec)が Laravel timeout より短くないか
6) Horizon運用なら horizon.php の timeout とキュー分離(heavy)を検討したか
7) 外部API/HTTPに timeout が設定されているか(無限待ちが無いか)
8) DBロック待ちや長いトランザクションが無いか(ロック・スロークエリを確認)
9) メモリ肥大で遅くなっていないか(chunk/cursor、–memory)
-
前の記事
Laravel『Invalid Blade Directive』の原因と対処法 2026.01.27
-
次の記事
記事がありません
コメントを書く