Laravel 開発約 5 分で読めます

Laravel によるモダナイゼーション:キュー、冪等性、安全な移行

トランザクションによる書き込み、再試行しても安全なジョブ、リハーサル済みのロールバック計画で、レガシーな PHP アプリケーションを段階的に刷新します。

アプリケーションのトランザクションがキューと独立したワーカーにデータを渡す様子

要点

業務機能を一つずつ移行します。トランザクションと制約でデータベースの不変条件を守り、キューのジョブを再試行しても安全にし、次のルートを移す前に配信とロールバックを確認します。

検証できるほど小さな範囲を選ぶ

段階的な移行は各リリースの範囲を小さくできますが、リスクがなくなるわけではありません。入力、出力、担当者がはっきりしている限定的な業務フローから始めましょう。トラフィックを移す前に、既存のデータモデル、外部への副作用、レポートとの依存関係を洗い出します。

エラー率、応答レイテンシ、キューの滞留時間、完了した注文数などの業務成果を基準値として記録します。制御された一部のリクエストを新しい実装に振り向け、明確な戻り道を残しておきます。依存関係上どうしても必要な場合を除き、ストレージ、フレームワーク、決済、UI を同時に移行するのは避けましょう。

重要な書き込みはトランザクションで行う

権限と入力を検証したうえで、有効な最小限の状態遷移をデータベーストランザクション内で保存します。「注文ごとに外部参照は一つ」といった不変条件には一意制約を使います。行ロックや条件付き更新は、正しく使えば同時に起きる状態遷移を守れます。

Redis のロックで作業を調整することはできますが、リースの期限切れ、クラッシュ、範囲を誤ったキーによって処理が重なることがあります。データベースの制約の代わりにはなりません。どのシステムがどの不変条件を担うかを文書化し、同時リクエストで検証しましょう。

データがコミットされてからディスパッチする

ワーカーは、元になったトランザクションがコミットされる前に動き出すことがあります。Laravel にはコミット後にディスパッチする仕組みがあり、ジョブが未コミットやロールバック済みのレコードを読むのを防げます。Laravel 12 のアプリケーションでの基本的なパターンは次のとおりです。

DB::transaction(function () use ($validated) {
    $order = Order::create($validated);
    ProcessOrder::dispatch($order->id)->afterCommit();
});

この例は意図的に不完全です。認可、重複リクエストの処理、業務上の検証はこの周りに置きます。また、コミット後のディスパッチでも、データベースのコミットと外部キューへの受け渡しの間の障害の隙間はなくなりません。確実な配信が必要なフローでは、トランザクショナル・アウトボックスと再試行するパブリッシャーを検討してください。

「少なくとも1回」実行される前提でジョブを設計する

ジョブは複数回実行されうるものと考えます。冪等性キーを保存し、完了した操作を記録し、外部プロバイダーに冪等性の仕組みがあればそれを使います。外部への副作用が成功する前にジョブを完了扱いにしてはいけません。結果が不確かな場合は、請求や発送をやみくもに繰り返すのではなく、突き合わせて確認します。

  • タイムアウト、再試行回数の上限、バックオフは操作ごとに設定します。
  • ワーカーのタイムアウトはキューの再試行間隔より短くし、停止処理の余裕も持たせて、試行の重複を減らします。
  • 失敗したジョブとキューの滞留時間を監視します。HTTP 202 は「受け付けた」という意味であり、「完了した」ではありません。
  • 長時間動くワーカーはリリース時に再起動し、意図したコードを読み込ませます。

障害とロールバックをリハーサルする

重複した Webhook、外部呼び出し後のワーカーのクラッシュ、データベースのデッドロック、キューの停止、デプロイの失敗を試します。HTTP レスポンスだけでなく、結果として残ったレコードと副作用を確認します。受け付けたまま完了しなかった処理を突き合わせる仕組みも用意しましょう。

両方のアプリケーションが稼働している間は、後方互換性のあるスキーマ変更を使います。リリース前にロールバックの条件を定め、新しいアプリケーションが書いたデータを旧アプリケーションが読めるか確認します。同期の設計を明示的に行うまでは、各レコードの信頼できる情報源を一つに保ちます。

よくある質問

処理を同期のままにすべきなのはどんなときですか?

呼び出し側がすぐに確定した結果を必要とし、処理が応答時間の予算内に収まるほど短い場合です。キューは後回しにできる処理に役立ちますが、配信、可視性、復旧について独自の責任が生じます。

コミット後のディスパッチなら、ジョブは必ずキューに届きますか?

いいえ。トランザクションのコミット前にジョブがディスパッチされるのは防げますが、コミットと外部キューへの受け渡しの間で障害が起きる可能性は残ります。確実な配信が必要なら、再試行するパブリッシャーと突き合わせを備えたトランザクショナル・アウトボックスを検討してください。

再試行によって支払いや注文が重複するのを防ぐには?

永続化した冪等性キー、データベースの制約、そして利用できる場合は外部プロバイダーの冪等性の仕組みを使います。結果を記録し、不確かな応答は突き合わせて確認します。短時間のロックだけでは、あらゆるクラッシュや再試行の隙間をカバーできません。

段階的な移行をロールバックしやすくするには?

限定的なフローを移し、以前の実装へ戻すルートを明確に保ち、後方互換性のあるスキーマ変更を使うことです。新しいアプリケーションが作ったレコードを旧アプリケーションが読めるか試してください。エラーと業務成果にもとづくロールバックの判断基準を、リリース前に定めておきます。

出典と参考資料

あわせて読みたい

レンダリング戦略を比較するか、Laravel への移行についてご相談ください。

Paul Edward

執筆:Paul Edward

PHP、Laravel、WordPress、AI を活用した Web システムを手がけるシニアフルスタック Web デベロッパー。

Paul について詳しく

Leave a Reply

Your email address will not be published. Required fields are marked *

簡単な確認を読み込んでいます…(JavaScript が必要です)

プロジェクトブリーフ ステップ 1/2 · 依頼内容

何を作りたいですか?

最初はひと段落あれば十分です。私に合わない仕事であれば正直にお伝えし、より適した方をご紹介します。

依頼内容

当てはまるものをすべて選んでください。

プラットフォーム

「わからない」でもまったく問題ありません。

何を作りたいですか?使う人のために、それは何をする必要がありますか?声に出して話すように書いてください。

0 / 1200

2 ステップ、1 分以内。