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

リトライだけでは不十分:PHP非同期ジョブのリコンシリエーション設計

PHPの非同期ジョブで、リトライだけに依存せず、期待される業務上の結果を検証し、不整合を検出して修復する方法を学びます。

PHPアプリケーションにおける非同期ジョブのリコンシリエーション図

非同期ジョブにより、インポート、同期、通知、ドキュメント生成、統合を疎結合にできます。しかし、コンシューマーがメッセージを処理したことは、業務上の結果が正しいことを必ずしも示しません。外部効果を確認する前に成功が記録された可能性、二つの手順の間に障害が発生した可能性、同じ操作が複数回実行された可能性があります。

PHPにおける非同期ジョブのリコンシリエーションは、この差を扱います。システムが達成するはずだったことと実際に起きたことの証拠を比較し、欠落や不一致を検出して、制御された修正を起動します。これはキュー、リトライ、冪等性の代替ではなく、独立した検証によってそれらを補完するものです。

技術的な実行は業務結果と同義ではない

技術的な実行は業務結果と同義ではない — guía visual de DedicatedPHP

タスクは例外なく終了しても、プロセスを未完了のまま残すことがあります。たとえば、アプリケーションが同期リクエストを作成し、コンシューマーが外部APIを呼び出したものの、ネットワーク切断により結論の出ない応答を受け取る場合です。冪等キーなしでリトライすると、重複を作成するおそれがあります。成功したと仮定すると、レコードが未同期のまま残る可能性があります。

また、メッセージング基盤の特定の配信セマンティクスを当然視してはなりません。再配信や重複実行が起こり得るかどうかは、ブローカー、その永続化設定、確認応答、コンシューマーの動作、および発生する障害に依存します。設計では、選択した技術においてこれらの特性を検証し、重複または順序変更が起こり得る場合は明示的に許容しなければなりません。

運用上の問いは「メッセージは消費されたか」だけではなく、「期待した効果が、該当する場合は一度だけ、かつ正しいデータで存在することを証明できるか」です。この証明には証拠のソースが必要です。外部システムから照会可能な応答、永続化されたリモートID、保存されたドキュメント、または確認済みの状態変更が該当します。

リトライ、冪等性、リコンシリエーション:異なる責務

リトライは、一時的な障害を扱います。一時的な利用不能、利用制限、短時間のロック、ネットワーク問題などです。試行回数の上限、段階的な遅延、エラー分類、対応が必要なメッセージの送信先を定義することが推奨されます。無期限のリトライは、データエラーを隠したり、外部障害を悪化させたりする可能性があります。

冪等性は、操作を繰り返しても安全にします。外部プロバイダーに送る安定した操作ID、データベースの一意制約、または効果を発生させる前のトランザクション検査で実現できます。これは効果が発生したことを意味しません。繰り返しても効果が増殖しないはずであることを意味します。

リコンシリエーションは、保留中、不完全、または矛盾した操作を見つけ、それぞれに対して何を行うかを決定します。外部効果、バッチ処理、複数システムの更新、または送信側アプリケーションだけでは受信を証明できない通信がある場合に、特に必要です。

  • 一時的と分類された障害を再試行するためにリトライを使用します。
  • リトライまたは再配信による効果の重複を防ぐために冪等性を使用します。
  • 最終状態を確認し、検出された差異を修復するためにリコンシリエーションを使用します。

操作をモデル化し、検証可能な証拠を保持する

保守しやすい設計では、三つの概念を分離します。要求されたジョブは、たとえば「注文452を同期する」という意図を表します。期待する効果は、「外部システムにバージョン7の注文が存在する」という観測可能な結果を定義します。確認は、その結果が存在する証拠、すなわちリモートID、バージョン、タイムスタンプ、検証済み応答、または後続照会の結果を保存します。

メッセージを公開する前に、永続的なデータベースに実行レコードを作成します。アプリケーションが自らのデータを変更してメッセージを公開する場合は、outboxパターンを検討してください。業務変更と保留イベントを同じトランザクションで保存し、後続プロセスに公開を委任します。これにより、ローカル変更を確定してメッセージを失うリスク、またはロールバックされた変更に対するメッセージを公開するリスクを減らせます。

レコードには少なくとも次を含める必要があります。

  • メッセージ、ログ、外部呼び出しの相関に使用する、不変かつ一意のoperation_id
  • 操作種別、影響を受けるエンティティ、期待する内容のバージョンまたはフィンガープリント。
  • 現在の状態、試行回数、次回試行可能時刻、タイムスタンプ。
  • 冪等キー、および存在する場合はリモートリソースID。
  • 要約した証拠と、秘密情報や不要な個人データを記録しない、応答またはエラーへの安全な参照。
  • クローズ、補償、破棄、または人によるレビューへのエスカレーションの理由。

たとえば、pendingprocessingawaiting_confirmationconfirmedretry_scheduledmanual_reviewcompensatednot_applicableといった明示的な遷移を定義します。各遷移には担当者と検証可能な条件が必要です。前の状態がpendingの場合にのみprocessingへ移行するような条件付き更新は、コンシューマー間の競合を減らします。

リコンシリエーションプロセスを構築する

リコンシリエーションは、スケジュールされたPHPコマンド、専用worker、または運用フローによって実行できます。時間ウィンドウを用いて動作させる必要があります。外部統合に通常数分かかる場合、数秒前に作成された操作を調べてはいけません。実際のレイテンシーデータでウィンドウを定義し、制限またはプロバイダーの変更時に見直します。

対象となる各操作について、あらかじめ定義した信頼できる情報源を比較します。ローカルデータベースは意図とデータバージョンの権威となり、外部システムはリソースを受信または作成したかどうかの権威となり得ます。宛先に対する信頼できる照会が存在しない場合、証拠は署名付き受領確認、プロバイダーID、または結果ファイルによる遅延検証となる可能性があります。

  1. 期待期限を超過した、未確認の操作を選択します。
  2. operation_id、冪等キー、または曖昧さのない業務キーを使い、効果が存在するかを確認します。
  3. リソースの存在だけでなく、関連フィールドとバージョンを比較します。
  4. ケースを、欠落、正しい、乖離、曖昧、または非該当として分類します。
  5. 許可されたアクションを実行し、その証拠とともに決定を保存します。

曖昧な結果を自動的に再キューイングへ変換してはいけません。呼び出しがリソースを作成した可能性があるにもかかわらず、信頼できる照会手段がない場合、リトライは課金、通知、またはドキュメントを重複させる可能性があります。その場合は自動アクションをブロックし、判断に十分なコンテキストを備えた例外パネルへケースを送ります。

新たな損害を生じさせずに修正する

アクションは不一致と誤判断のコストに依存します。効果が欠落しており、操作が冪等である場合、再キューイングが適切です。補償では、無差別な技術的削除ではなく、明示的な業務操作によって誤った効果を元に戻せます。曖昧さ、バージョン競合、または財務上の影響がある場合は、レビュー対象としてマークすることが望ましいです。エンティティが文書化されたルールに従ってキャンセルまたは置換された場合は、非該当としてクローズすることが有効です。

手動修復も追跡可能にしなければなりません。誰が決定したか、どの証拠を確認したか、どのアクションを適用したか、結果がどうなったかを残します。権限を制限し、エンティティ、バージョン、宛先、重複リスクを表示せずに操作を実行するボタンは避けてください。

設計を検証する可観測性とテスト

operation_idで相関付けられたログにより、Web、worker、外部サービスをまたぐ操作の追跡が容易になります。有用なメトリクスは例外だけにとどまりません。保留操作の経過時間、手動レビュー件数、乖離率、原因別リトライ数、確認までの時間を測定します。アラートは個々の孤立したエラーではなく、蓄積、経過時間、または期限超過で発報すべきです。

代表的な障害をテストします。外部効果の後かつ確認を永続化する前の障害、重複実行、順序外メッセージ、workerの再起動、リモート結果が不確かなtimeout、長時間の利用不能、操作が保留中に発生するバージョン変更です。テストでは最終状態だけでなく、重複がないことと、保存された証拠の品質も確認する必要があります。

例:外部システムへのレコード同期

PHPアプリケーションが顧客レコードを同期すると仮定します。変更時に、安定した識別子と期待するローカルバージョンを持つsync_customer操作を作成します。workerはこれらの値を冪等キーとして宛先に送信します。有効な確認を受け取れば、リモートIDを永続化し、状態をconfirmedへ変更します。

リクエスト送信後にtimeoutが発生した場合、workerは操作をawaiting_confirmationのままにします。リコンシリエーターは冪等キーで宛先を照会します。同じバージョンが見つかれば確認します。見つからなければ、新しい送信をスケジュールします。異なるバージョンが見つかった場合は、他方のシステムで正当に変更された可能性があるデータを上書きするのではなく、ケースをレビュー対象としてマークします。

プロセスを書き直さずにリコンシリエーションを導入するチェックリスト

プロセスを書き直さずにリコンシリエーションを導入するチェックリスト — guía visual de DedicatedPHP
  • 既存ジョブを棚卸しし、外部効果を生むもの、金銭や規制対象データに影響するもの、または再実行が難しいプロセスを優先します。
  • 各種別について、期待する効果、信頼できる情報源、それを確認可能にする証拠を文書化します。
  • 具体的なブローカーとコンシューマーで、障害、遅延確認応答、永続化、再配信、メッセージ順序に対して何が起こるかを検証します。
  • 安定したoperation_idを追加し、メッセージ、ログ、外部呼び出し、状態レコードに伝播させます。
  • 状態、試行、期限、冪等キー、証拠を持つ操作テーブルを導入します。必要に応じて観測モードから開始します。
  • リトライ、確認、補償、エスカレーション、非該当クローズのための条件付き遷移と文書化されたポリシーを定義します。
  • 時間ウィンドウとパイロット対象の一操作種別に限定したリコンシリエーターを実装します。
  • 修正を自動化する前に、重複、曖昧なtimeout、手順間の障害、順序変更、再起動のケースで検証します。
  • 経過時間、エンティティ、証拠、推奨アクションを備えた例外パネルまたは照会を作成します。
  • メトリクス、停滞した操作、手動判断を定期的に見直し、期限、ルール、制御を調整します。

段階的な導入により、アーキテクチャ全体を置き換えずに信頼性を改善できます。まず不確かな操作を可視化し、次に結果を確認し、最後に安全性を証明できる修正だけを自動化します。

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