Laravel Queue 設計でリトライと冪等性を失敗しない実務ポイント

Laravel Queue 設計でJob、retry、failed jobs、冪等性を整理する図

LaravelのQueue 設計は失敗と再実行まで含める

Queue 設計で迷う場面は、Laravel実務ではよく出てきます。通知送信、外部API連携、CSV取込、集計処理などです。処理をQueueに投げればレスポンスは速くなります。しかし、失敗時の再実行や重複実行を考えないまま非同期化すると、あとで障害調査が難しくなります。

結論から言うと、Queueは「重い処理を後ろに逃がす仕組み」だけではありません。そのため、LaravelのQueue 設計では、Jobの責務、retry、timeout、failed jobs、冪等性、監視をセットで決めます。

この記事では、LaravelのQueue 設計を実務で扱うときの判断基準を整理します。また、レビューで見られやすい失敗例と改善ポイントも扱います。

Queue 設計で向いている処理を見極める

まず、Queue 設計では、Queueに入れるべき処理を見極めます。Laravelでは公式ドキュメントのQueuesで、Job、Worker、failed jobs、retryなどの仕組みが整理されています。

ただし、仕組みがあるからといって何でもQueue化するのは危険です。ユーザーに結果をすぐ返す必要がある処理、トランザクション内で完結すべき処理、順序保証が強く必要な処理は、同期処理のままにした方が安全な場合があります。

処理Queue向きか判断理由
メール送信向いている多少遅れてもよく、失敗時に再実行しやすい
外部APIへの通知条件付きで向いているリトライと冪等性が設計できるなら有効
注文確定のDB更新慎重に判断ユーザーへの結果表示や整合性に直結する
CSV取込後の集計向いている時間がかかり、進捗管理や再実行と相性がよい

つまり、Queue 設計では「遅らせてもよいか」だけでは足りません。「失敗しても安全に再実行できるか」を見る必要があります。

Queue 設計で最初に決めるJobの責務

Jobは小さければよい、という話ではありません。一方で、何でも詰め込んだ巨大Jobは、失敗理由が追いにくくなります。

例えば、注文完了後に「メール送信」「在庫連携」「外部CRM通知」「集計更新」を1つのJobにまとめるとします。この場合、CRM通知だけが失敗しても、メール送信まで再実行される恐れがあります。

そこで、再実行単位に合わせてJobを分けます。失敗時に同じ単位でやり直したい処理を1つのJobにする、という考え方です。

// 役割が大きすぎる例
class HandleOrderCompleted implements ShouldQueue
{
    public function handle(): void
    {
        $this->sendMail();
        $this->notifyCrm();
        $this->updateSummary();
    }
}

// 再実行単位に分ける例
SendOrderCompletedMail::dispatch($orderId);
NotifyOrderToCrm::dispatch($orderId);
UpdateOrderSummary::dispatch($orderId);

ただし、分けすぎるとJob間の依存関係が複雑になります。そのため、レビューでは「失敗時にどこからやり直すか」「順序が必要か」「同じデータを何度も読んでいないか」を確認します。

Queue 設計ではretryとtimeoutを処理の性質から決める

Laravel Queueでは、Jobごとに試行回数やタイムアウトを設定できます。Queue 設計で詳しい設定を見るなら、Laravel公式のmax job attempts and timeoutが参考になります。

まず避けたいのは、全Jobに同じretry回数を設定することです。例えば、メール送信の一時的な失敗と、データ不整合による失敗では、再実行すべきかどうかが違います。

失敗の種類retry方針理由
外部APIの一時的なタイムアウト数回リトライする時間を置けば成功する可能性がある
認証情報の設定ミス短く失敗させるリトライしても成功しにくい
入力データの不備原則リトライしない同じデータでは同じエラーになる
DBデッドロック限定的にリトライする競合が解消すれば成功する場合がある

また、timeoutは長くすれば安全になるわけではありません。長すぎるtimeoutはWorkerを詰まらせます。逆に短すぎるtimeoutは正常な処理まで失敗扱いにします。

そのため、通常時の処理時間、外部APIのタイムアウト、DBロック待ち、Worker数を見て決めます。さらに、失敗時に通知や監視へつなげる設計も必要です。

Queue 設計では冪等性がないJobをリトライしない

Queue 設計で特に重要なのが冪等性です。冪等性とは、同じ処理が複数回実行されても結果が壊れない性質です。

例えば、ポイント付与Jobが失敗後にリトライされたとします。すでにポイントを付与した後で通信エラーが起きていた場合、再実行で二重付与になるかもしれません。

そこで、Jobの入口で処理済みかどうかを確認します。また、外部APIへ送る場合は、idempotency keyや一意なリクエストIDを使えるか確認します。

class GrantOrderPoint implements ShouldQueue
{
    public function handle(): void
    {
        if (PointHistory::where('order_id', $this->orderId)->exists()) {
            return;
        }

        DB::transaction(function () {
            PointHistory::create([
                'order_id' => $this->orderId,
                'reason' => 'order_completed',
            ]);

            // ポイント残高の更新
        });
    }
}

もちろん、この例だけで十分とは限りません。同時に同じJobが走る可能性があるなら、DB制約やロックも検討します。つまり、アプリケーション側のチェックだけに頼らないことが重要です。

Queue 設計ではfailed jobsを運用で戻せる形にする

Queueは失敗します。そのため、Queue 設計にはfailed jobsの扱いも含めます。Laravelのfailed jobs機能は便利ですが、ただ保存しているだけでは運用できません。

まず、失敗したJobが何の業務データに紐づくのかを追えるようにします。order_id、user_id、external_request_idなど、調査に必要な識別子をログやJob payloadから追える状態にします。

さらに、手動再実行してよいJobか、データ修正後でないと再実行してはいけないJobかを分けます。ここが曖昧だと、障害対応時に「とりあえずretry」して二次被害を起こします。

  • 失敗理由が一時的か恒久的かを分類する
  • 再実行前に確認すべきDB状態を決める
  • 再実行しても二重処理にならない設計にする
  • 失敗が一定件数を超えたら通知する
  • 失敗Jobを放置しない運用担当を決める

なお、Queueの監視にはLaravel Horizonも選択肢になります。Redis Queueを使う場合は、待ち行列、処理量、失敗状況を見える化しやすくなります。

Queue 設計ではバッチ処理とスケジューラの境界も決める

LaravelではQueueだけでなく、Task Schedulingもよく使います。バッチ処理では、この2つの役割分担が曖昧になりがちです。

例えば、毎日夜間に対象データを抽出し、1件ずつ外部APIへ送る処理があるとします。この場合、スケジューラは「対象を探してJobを投入する役割」に寄せます。一方で、Queue Jobは「1件分を安全に処理する役割」に寄せると分かりやすくなります。

その結果、途中で一部だけ失敗しても、失敗した単位だけ再実行しやすくなります。また、処理量が増えた場合もWorker数やQueue分割で調整できます。

Queue 設計のレビュー観点

実務レビューでは、コードが動くかだけでなく、失敗時に運用できるかを見ます。特に、外部API連携や金額、在庫、通知に関わるJobでは重要です。

  • Jobの責務が再実行単位と合っているか
  • retry回数とtimeoutが処理の性質に合っているか
  • 同じJobが複数回実行されても壊れないか
  • DB制約や一意キーで二重処理を防げているか
  • 失敗時に業務IDから調査できるか
  • 手動再実行してよい条件が明確か
  • Worker停止やQueue滞留を検知できるか
  • スケジューラとQueue Jobの責務が混ざっていないか

また、Queueは本番運用で差が出る領域です。ローカルでは問題なく見えても、処理量、外部APIの遅延、Worker数、メモリ使用量によって挙動が変わります。そのため、設計段階で監視と復旧手順まで確認します。

まとめ:Queue 設計は失敗後の復旧まで決める

Queue 設計では、非同期化そのものよりも、失敗した後に安全に戻せることが重要です。まず、Jobの責務を再実行単位に合わせます。次に、retryとtimeoutを処理の性質から決めます。さらに、冪等性、failed jobs、監視、手動復旧まで設計に含めます。

LaravelのQueueは、メール送信や通知だけでなく、業務系システムの外部連携、バッチ処理、運用改善でもよく使われます。そのため、非同期処理や運用まで見据えた設計経験は、案件選びでも強みになります。

Laravelや非同期処理、保守改善の経験を次の案件でどう活かすか迷っている方は、技術領域や担当範囲を整理するところから相談できます。

IaC INP PM PMO PMP UX Webディレクター インフラエンジニア キャリアチェンジ フロントエンドエンジニア