Laravelのジョブがタイムアウトした際に再実行しない方法

はじめに

以前、Laravelのジョブを利用する機会があったのですが、複数のワーカー起動時にジョブがタイムアウトした場合、再実行されエラーが発生する事象がありました。
原因としては、設定の不備だったのですが、解消する為の方法をご紹介いたします。

環境

  • PHP 8.3.7
  • Laravel v11.16.0
  • PostgreSQL 13.15

サンプルコード

<?php

namespace App\Jobs;

use Exception;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\MaxAttemptsExceededException;
use Illuminate\Queue\TimeoutExceededException;
use Illuminate\Support\Facades\Log;

class TimeOutTest implements ShouldQueue
{
    use Queueable;

    public function handle(): void
    {
        Log::info('ジョブ:起動');

        // タイムアウトを発生させる為、120秒待機
        sleep(120);

        Log::info('ジョブ:成功');
    }

    public function failed(Exception $e): void
    {
        if ($e instanceof TimeoutExceededException) {
            Log::info('ジョブ:タイムアウト');
        } elseif ($e instanceof MaxAttemptsExceededException) {
            Log::info('ジョブ:最大試行回数超過');
        }

        Log::info('ジョブ:失敗');
    }
}

サンプルコード実行

ワーカー起動

タイムアウトは120秒を設定し、2つ立ち上げます。

php artisan queue:work --timeout=120
ジョブ起動

まず、コマンドラインからTinkerを起動し、

php artisan tinker

以下でジョブを実行します。

App\Jobs\TimeOutTest::dispatch();

※尚、キューの保存先はデータベースを利用しております。

実行結果

ワーカー出力結果

ワーカー1の起動後、91秒後にワーカー2が起動してしまっています。

  • ワーカー1
INFO Processing jobs from the [default] queue.

2025-08-08 20:00:00 App\Jobs\TimeOutTest ... RUNNING
2025-08-08 20:02:00 App\Jobs\TimeOutTest ... 2分 FAIL
  • ワーカー2
INFO Processing jobs from the [default] queue.

2025-08-08 20:01:31 App\Jobs\TimeOutTest ... RUNNING
2025-08-08 20:01:31 App\Jobs\TimeOutTest ... 75.12ms FAIL
実行ログ

ワーカー1の起動後、91秒後にワーカー2が起動し、最大試行回数超過エラー(MaxAttemptsExceededException)が発生しております。

また、ワーカー1は120秒後にタイムアウトとなるのですが、ワーカー2が失敗したジョブを先にテーブルに登録してしまっている為、uuidの重複エラーも発生しております。

[2025-08-08 20:00:00] local.INFO: [][CONSOLE][---] e4724ab ジョブ:起動
[2025-08-08 20:01:31] local.INFO: [][CONSOLE][---] 18bd3d4 ジョブ:最大試行回数超過
[2025-08-08 20:01:31] local.INFO: [][CONSOLE][---] 18bd3d4 ジョブ:失敗
[2025-08-08 20:01:31] local.ERROR: [][CONSOLE][---] 18bd3d4 エラーファイル:/var/www/html/vendor/laravel/framework/src/Illuminate/Queue/MaxAttemptsExceededException.php
[2025-08-08 20:01:31] local.ERROR: [][CONSOLE][---] 18bd3d4 エラー行:24
[2025-08-08 20:01:31] local.ERROR: [][CONSOLE][---] 18bd3d4 エラーの原因:App\Jobs\TimeOutTest has been attempted too many times.
[2025-08-08 20:02:00] local.INFO: [][CONSOLE][---] e4724ab ジョブ:タイムアウト
[2025-08-08 20:02:00] local.INFO: [][CONSOLE][---] e4724ab ジョブ:失敗
[2025-08-08 20:02:00] local.ERROR: [][CONSOLE][---] e4724ab エラーファイル:/var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connection.php
[2025-08-08 20:02:00] local.ERROR: [][CONSOLE][---] e4724ab エラー行:808
[2025-08-08 20:02:00] local.ERROR: [][CONSOLE][---] e4724ab エラーの原因:SQLSTATE[23505]: Unique violation: 7 ERROR: 重複したキー値は一意性制約"failed_jobs_uuid_unique"違反となります

e4724ab のプロセスがワーカー1、18bd3d4 のプロセスがワーカー2です。

原因

マニュアルを確認したところ、retry_afterの値がtimeoutの値より少なく設定されていたのが原因で再実行されてしまっていた様です。

Warning! –timeout値は、常にretry_after設定値より少なくとも数秒短くする必要があります。これにより、フリーズしたジョブを処理しているワーカは、ジョブが再試行される前に常に終了します。
–timeoutオプションがretry_after設定値よりも長い場合、ジョブは2回処理される可能性があります。

修正箇所

キューの保存先がデータベースの場合、設定としては以下の箇所のretry_afterとなります。
(他にAmazon SQSやRedis等の設定がある為、利用しているものに合わせて下さい)

  • config/queue.php

既に環境変数を参照する作りとなっている為、こちらは修正せず、.evnに環境変数を追加します。

'database' => [
    'driver' => 'database',
    'connection' => env('DB_QUEUE_CONNECTION'),
    'table' => env('DB_QUEUE_TABLE', 'jobs'),
    'queue' => env('DB_QUEUE', 'default'),
    'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90), // デフォルト90秒
    'after_commit' => false,
],

※先述の再実行されたワーカーが約90秒後に起動していたのは、このデフォルト値が原因でした。

  • .env

config/queue.phpで参照していたDB_QUEUE_RETRY_AFTERを追加し、
ワーカーのタイムアウト120秒より数秒長い125秒を設定します。

DB_QUEUE_RETRY_AFTER=125

設定追加後、コード反映の為、ワーカーを再起動し、再度ジョブを実行します。

修正後 実行結果

ワーカー出力結果

ジョブが再実行されなくなった事で最大試行回数超過エラー(MaxAttemptsExceededException)エラーとuuidの重複エラーも解消されました。

  • ワーカー1
INFO Processing jobs from the [default] queue.

2025-08-08 20:10:00 App\Jobs\TimeOutTest ... RUNNING
2025-08-08 20:12:00 App\Jobs\TimeOutTest ... 2分 FAIL
Killed

※ワーカーのプロセスがKillされるので実運用ではSupervisorを使い、再度プロセスを立ち上げる必要があります。

  • ワーカー2
INFO Processing jobs from the [default] queue.
実行ログ
[2025-08-08 20:10:00] local.INFO: [][CONSOLE][---] 29f7b19 ジョブ:起動 
[2025-08-08 20:12:00] local.INFO: [][CONSOLE][---] 29f7b19 ジョブ:タイムアウト 
[2025-08-08 20:12:00] local.INFO: [][CONSOLE][---] 29f7b19 ジョブ:失敗 

29f7b19 のプロセスがワーカー1です。

おわりに

Laravelのジョブは時間の掛かるバッチ等をリアルタイムでバックグラウンド処理する事ができ、非常に便利な機能ですが、ちょっとした落とし穴もあるので、隅々までマニュアルを確認する事の大切さを再認識いたしました。

--------------------------
開発支援・技術研修のご要望・ご相談はこちらから
--------------------------
【この技術ブログを読んだエンジニアの皆様へ】
カサレアルブログをお読みいただき、ありがとうございます!

私たちは、常に新しい技術に挑戦し、ユーザーのニーズに応えるサービスを提供しています。
もし、当社の技術への情熱や、会社・チーム・社員の雰囲気に共感いただけたなら、
ぜひ私たちと一緒に働きませんか?
現在、株式会社カサレアルでは事業拡大に伴い、新たな仲間となるエンジニアを積極的に募集しています。

少しでも興味をお持ちいただけましたら、まずは弊社のことを知っていただけると嬉しいです。
▼採用サイト
https://www.casareal.co.jp/recruit/career
▼社員インタビュー
https://hrmos.co/pages/casareal/jobs/0000016
▼エンジニアの仲間になる! エントリーはこちらから
https://hrmos.co/pages/casareal/jobs

皆様のエントリーを心よりお待ちしています!

GitLabCI(2/3) パイプライン作成