コンテンツへスキップ
DedicatedPHP 接触

PHPプロセスが途中で止まったときの補償設計

システム間で失敗したPHPフローをどう復旧するかを解説します。再試行と補償の使い分け、結果の記録、例外のエスカレーション方法を紹介します。

ローカルデータベースと外部API間のステップ、再試行、補償を示すPHPプロセスの編集用図

ビジネスプロセスでは、処理の一部が完了した後、全体が終わる前に失敗することがあります。たとえば、PHPアプリケーションがデータベースにアカウントを作成できても、外部サービスへのプロビジョニングに失敗する場合です。このとき、何も考えずにフローを再実行すると効果が重複するおそれがあり、失敗として処理を終えるとシステム間に不整合が残る可能性があります。PHPによる分散プロセスの補償では、複数のシステムが単一の原子的トランザクションに参加していると仮定せず、こうした部分的な結果への対応を定義できます。

重要なのは、起きたことをどう取り消すかだけではありません。効果を正確に元に戻せない場合もあり、補償自体が失敗することもあります。設計では、処理の実際の状態を明らかにし、復旧を可能にするとともに、人による対応が必要なタイミングを示す必要があります。

ローカルトランザクション、再試行、補償はそれぞれ異なる問題を解決する

ローカルトランザクション、再試行、補償はそれぞれ異なる問題を解決する — guía visual de DedicatedPHP

データベーストランザクションが保護するのは、そのデータベース内の変更です。複数の行を保存する処理で、COMMITの前に操作が失敗した場合は、ROLLBACKを実行して変更を破棄できます。しかし、このロールバックで送信済みのメールを取り消すことはできず、APIがすでに受理したリクエストを自動的にキャンセルすることも、別のデータベースで確定した変更を元に戻すこともできません。

再試行とは、タイムアウトやサービス停止など、一時的な問題で失敗した可能性のある操作を再実行することです。再実行した場合の影響が分かっている場合に限り、適切な方法となります。応答が失われてもリクエスト自体は外部システムに届いていた可能性があります。冪等性キーや状態照会の仕組みがなければ、再試行によって重複が発生するおそれがあります。

補償とは、すでに完了したステップの効果を相殺するビジネスアクションです。たとえば、作成したばかりのアクセス権を無効にします。これは技術的なロールバックではなく、必ず元の状態を完全に復元できるものでもありません。一方、照合では、ローカルに記録された内容と関係するシステムの状態を比較し、不一致の検出と解決に役立てます。再試行や補償があっても、照合が必要になる場合があります。

各操作を、状態を観測できるステップの連続としてモデル化する

例外ハンドラーを書く前に、フローを独立したステップに分解して記述します。各ステップについて、変更するシステム、成功とみなす結果、操作の識別方法、安全に再実行できるか、完了できない場合の代替手段を明確にします。また、一貫性を保つ必要がある効果を定義します。このビジネスルールが、復旧の指針となる不変条件です。

アプリケーション内に、操作と各ステップの記録を永続化します。シンプルなモデルには、安定した操作ID、全体の状態、ステップ名、結果、試行回数、タイムスタンプ、最後に発生した重要なエラーなどを含められます。状態値には、保留中、進行中、完了、失敗、補償中、要確認などを使うと、判明していることと、まだ確認できていないことを区別できます。

ローカルの変更と作業記録を連動させる必要がある場合は、両者を原子的に保存します。作業キューで後から外部呼び出しを処理できます。アウトボックスパターンは、次のステップを開始する通知が失われたままデータベース変更だけが確定する事態を防ぐ方法の一つです。ただし、ネットワーク障害をなくすものではありません。コンシューマーは引き続き、メッセージの重複や結果が不確かな状況に対応する必要があります。

ネットワーク呼び出しの間、データベーストランザクションを開いたままにしないでください。外部呼び出しには遅延があり、応答が滞る可能性もあります。トランザクションを長時間維持するとリソースを占有するうえ、2つのシステムが原子的になるわけでもありません。明示的に定めた順序でローカル処理を確定し、続行または補償に必要な情報を記録します。

明示的で範囲が限定され、冪等な補償を設計する

効果を生む各ステップについて、補償が可能か、いつ実行するかを決めます。補償は必ずしも文字どおりの逆操作ではありません。アカウントに通知が届いた後であれば、メッセージが読まれた事実を消すことはできません。訂正を記録する、またはキャンセルを通知するのが適切な場合があります。操作が不可逆である場合や、法的・金銭的な影響を伴う場合は、フローを停止してエスカレーションするのが正しい対応かもしれません。

  • 冪等性: 同じ補償を2回実行しても、追加の効果が生じないようにします。システムが対応している場合は、安定した操作IDまたは冪等性キーを使い、結果を記録します。
  • 順序: どのステップを、どの順序で補償するかを定義します。通常は実行順の逆に戻しますが、依存関係やビジネスルールによって別の順序が必要になることもあります。
  • 上限: どのエラーを再試行するか、何回試すか、いつ停止するかを定めます。試行間に待機時間を設け、無制限の再試行は避けます。
  • 不確実性: APIがタイムアウトした場合、作成処理を再実行したり、誤った補償を実行したりする前に、状態を照会するか冪等性キーを使います。

補償ロジックは、特定可能なビジネス操作として実装します。例外を捕捉したときにデータを削除するだけの汎用ブロックにしてはいけません。何が起きたかを説明し、対応がプロダクトのルールに沿っていると確認するために必要な履歴を保持します。

仮想例:2つのシステムでアカウントを作成する

ローカルでアカウントを作成し、外部プロバイダーにワークスペースの有効化を依頼するフローを想定します。アプリケーションは操作IDを割り当て、アカウント作成を保留中として記録します。ローカルトランザクション内でアカウントと、プロビジョニングを依頼する作業を保存します。PHPワーカーが作業を処理し、外部IDとリクエスト結果を記録します。

プロバイダーが有効化を確認したら、フローを完了としてマークします。既知の一時エラーが返された場合は、同じ冪等性キーを使い、上限を設けて再試行できます。応答が不確かな場合、アプリケーションはまずワークスペースが存在するかを確認します。プロバイダーが作成を確認したものの、その後のローカル要件によってアカウント作成を完了できない場合、システムは補償として無効化を依頼し、その結果を保存できます。

無効化に失敗した場合、アカウント作成が取り消されたと宣言してはいけません。操作は、復旧待ちまたは要確認といった可視化された状態にし、ローカルIDと外部ID、試行回数、エラーを記録します。運用チームには、プロバイダーの状態を確認し、許可された対応を判断して、結果を記録する手順が必要です。この例は仮想的なものです。具体的な操作は、APIの機能とビジネスルールによって異なります。

補償も失敗した場合:可視化と確認

分散フローでは、すべての操作を自動的に取り消せると保証できません。再試行上限に達した場合、外部から曖昧な応答が返った場合、または補償に失敗した場合に備え、明示的な処理経路を設計します。不整合を隠さずに状態を保持し、未完了の操作をユーザーに完了済みとして表示しないようにします。

サポートまたは運用担当者向けに、ステップごとの状態、最後のエラー、相関ID、実行可能な操作を表示する画面を用意します。人による確認にはアクセス制御を設け、判断の監査記録を残します。処理の停滞、補償の失敗、運用上許容できる時間を超えた不一致を検出するため、メトリクスとアラートを設定します。定期的な照合作業でローカルの記録と外部システムを比較し、不一致の解決に向けたケースを起票できます。

部分的な障害をテストし、補償が必要か判断する

部分的な障害をテストし、補償が必要か判断する — guía visual de DedicatedPHP

テストでは成功経路だけでなく、各効果の前後でのエラー、遅延応答、結果不明のタイムアウト、重複メッセージ、プロセスの中断、補償中の障害もシミュレーションします。ワーカー再起動後に永続化された状態を確認し、再試行によってアカウントなどの操作が重複しないことを検証します。

  • 操作IDと各ステップの結果は保持されるか。
  • メッセージが重複しても、効果を重複させず同じ結果になるか。
  • リクエストの応答が不確かなとき、外部の状態を照会するか。
  • 補償は想定した順序で実行され、繰り返しても安全か。
  • 補償の失敗時に操作が可視化され、確認フローに送られるか。
  • 後からローカル状態と外部システムを照合できるか。
  • レスポンスコードだけでなく、ビジネス上の不変条件もテストしているか。

フローが単一のシステムだけに関わり、ローカルトランザクションですべての変更を保護できるなら、そのトランザクションとシンプルな復旧で十分な場合があります。既知の保証のもとで繰り返せる外部効果であれば、冪等な再試行と照合で足りることもあります。確定済みのステップを相殺してビジネスルールを守る必要がある場合は、補償を定義します。部分状態を検出できずに残すコストが、記録、テスト、運用手順を維持するコストを上回るなら、追加の複雑さを導入する価値があります。

これらのアイデアをあなたのプロジェクトに活用してみませんか?あなたのPHPプラットフォームについて話し合いましょう。
関連サービスを見る