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

タイムアウトと再試行に備えるPHPの冪等性

タイムアウト、並行処理、応答消失に対応して再実行可能なPHP操作を設計し、決済・注文・外部呼び出しの重複を防ぎます。

再試行、状態、外部呼び出しを制御するために冪等性キーを使うPHP APIの編集用図解

タイムアウトは、操作が失敗したことを示すものではありません。クライアントが期限内に応答を受信しなかったことを確認するだけです。サーバーが注文を作成済みである場合、決済プロバイダーが請求を受け付け済みである場合、または非同期プロセスが引き続き実行中である場合があります。クライアントが制御なく再試行すると、同じビジネス上の意図が重複した結果を生む可能性があります。

PHPの冪等性は、技術的な繰り返しを照会、またはすでに得られた結果の返却へと変換します。これは、すべての重複を無視することでも、ユーザーが二度クリックしないことだけを信頼することでもありません。クライアント、API、永続化層、そして該当する場合は外部システムの間における明示的な契約です。

問題:応答は失われても、結果は残る

問題:応答は失われても、結果は残る — guía visual de DedicatedPHP

購入を確定するエンドポイントを考えてみましょう。アプリケーションはリクエストを検証し、注文を記録し、請求を要求して、応答を準備します。クライアントが受信する直前に接続が切断されます。同じフォームを再送しても、エンドポイントは内容から同じ購入であると推測できません。同じ商品を含む二つの注文は、有効で異なる意図であり得るためです。

この問題は、ユーザー登録、クレジットの割り当て、文書の発行、同期、webhook、管理操作でも発生します。区別すべき要素は三つあります。

  • ビジネス上の意図:「この特定の購入を確定したい」。
  • 技術的なリクエスト:ヘッダー、本文、認証コンテキストを伴うHTTP送信。
  • 実行試行:各内部処理、キューの再試行、またはプロバイダーへの呼び出し。

冪等性キーが識別するのは意図であり、HTTP接続やサーバー側の各試行ではありません。そのため、ネットワーク再試行をまたいで存続し、フローで必要な場合はプロセスの再起動もまたいで存続しなければなりません。

冪等性が必要な操作と不要な操作

作成、確定、請求、送信、予約、通知、または重大な結果を伴うリソース変更を行う操作を優先してください。POST /payments、注文の確定、webhookの受信は明確な候補です。複数回配信される可能性があるキュージョブも該当します。

通常、純粋な読み取りには冪等性キーは必要ありません。更新には別のセマンティクスがあります。PUT /profiles/42のように望ましい状態を設定する操作は、同じ表現によってリソースが変わらないなら、設計上冪等にできます。一方、「残高を加算する」のような操作は、特定のHTTPメソッドを使うだけでは冪等になりません。

また、他のルールの代替としてキーを使うべきではありません。限られた在庫で両立する二つの予約を防ぐには、ドメイン不変条件、並行性制御、予約ポリシーが必要です。分散環境でタスクを一度だけ実行する場合、実際の配信は通常少なくとも一回です。コンシューマーは重複を許容しなければなりません。

キーと永続レコードの設計

クライアントは、ビジネス上の意図が生じた時点で不透明かつ十分に予測困難なキーを生成し、再試行できる間は保持し、たとえばIdempotency-Keyで送信すべきです。サーバーが受信のたびに生成すると、その後の繰り返しに関連付けられません。内部フローでは、キーをビジネスイベントの安定した識別子から導出できます。

そのスコープには、アクターまたはtenantと操作を含める必要があります。同じ文字列が、二つのアカウント間や「注文作成」と「返金発行」の間で衝突してはなりません。実際の再試行期間およびドメインのリスクに沿った保持期間を定義してください。レコードを早く削除しすぎると重複への道が再び開かれ、無期限に保持するとコストが増え、プライバシーおよび削除ポリシーが必要になります。

最小限の永続化モデルには、以下が含まれます。

  • セキュリティスコープまたはtenant、操作名、冪等性キー。
  • 正規化されたペイロードの暗号学的ハッシュ。
  • 状態:processingcompletedfailed、または外部確認が不確実な場合のpending
  • 繰り返し返却されるレスポンスコードと本文。
  • 作成されたリソースの識別子、内部相関、外部プロバイダーの参照。
  • 作成、更新、期限切れの日時。

ハッシュは重要なエラーを防ぎます。それは、同じキーを異なるデータで再利用することです。この場合は競合を返し、新しいペイロードを処理しないでください。比較を信頼できるものにするには、順序に意味のないフィールドを正規化し、意図の一部ではない変動するメタデータを除外します。

PHPフロー:結果を生む前に予約する

保護は、スコープ、操作、キーに対するデータベースの一意制約で裏付けなければなりません。先に照会してから挿入するだけでは不十分です。二つの同時リクエストがレコードの不在を確認し、同時に続行できるためです。

推奨されるフローは、原子的に予約することです。挿入に成功した場合、そのプロセスが実行の初期所有者です。一意性競合が発生した場合は、既存レコードを読み取り、ハッシュを検証し、その状態に応じて処理します。完了済みの結果では、永続化された応答を正確に返します。進行中の操作では、保留状態を返すか、再照会前に限定された時間だけ待機できます。

begin transaction
insert idempotency_records(scope, operation, key, payload_hash, status)
values (?, 'create_order', ?, ?, 'processing')
-- 一意制約が所有者を決定する
commit

if reservation_was_created:
    result = execute_business_operation()
    persist_completed_response(result)
else:
    record = load_existing_record()
    assert_same_payload_hash(record)
    return replay_or_pending(record)

プロバイダーへの遅い呼び出しの間、トランザクションや行ロックを開いたままにしないでください。これは処理能力を低下させ、長時間のロックを生む可能性があります。代わりに、短いトランザクションで予約し、ローカル状態を確定します。外部効果とローカルレコードを調整する必要がある場合は、送信指示もトランザクションテーブルに保存し、別途処理します。このパターンは再試行をなくしませんが、記録済みの意図を失わずに保留中の作業を回復できます。

並行性、タイムアウト、不確実な状態

同じキーを持つ二つのリクエストが、数ミリ秒差で到着する可能性があります。一意制約が、どちらが操作を予約するかを決定します。二つ目は別の外部効果を開始してはなりません。状態がprocessingまたはpendingの間は、結果を照会するための識別子を含めて202を返せます。契約上同期応答が必要であれば、限定的に待機してレコードを再読込できます。

何らかの結果を開始する前の失敗では、再現可能なエラーでfailedを設定できます。しかし、外部システム呼び出し時のタイムアウトは不確実性を生みます。自動的に失敗としてマークしたり、何も考慮せずに指示を再送したりすることは正しくありません。存在する場合は送信済みリクエストの参照を保存し、その参照を通じてプロバイダーに照会し、結果を照合してください。確認がない間はpendingを維持し、結果がまだ確定していないことを伝えます。

外部呼び出しにも安定した参照が必要です。プロバイダーが独自の冪等性キーをサポートする場合は、同じ意図に関連付くキーを伝播させてください。サポートしない場合は、加盟店識別子、後続の読み取り、定期的な照合、曖昧なケースに対する運用手順を使います。ローカルトランザクションで、データベース書き込みと独立したリモートAPIを原子的にすることはできません。

冪等性キーで解決できないこと

冪等性は、認識済みの意図の繰り返しを防ぎますが、不可逆な結果をどのように取り消すかは決めません。物理的な発送、すでに決済された送金、ユーザーが閲覧済みの通知では、補償、取消、または手動対応が必要になる可能性があります。これらのアクションは、権限、状態、監査を備えた明示的なビジネスプロセスとして設計してください。

修正と再試行も混同してはなりません。エラー後にユーザーが住所、金額、商品を変更する場合、それは新しい意図であり、新しいキーを使用しなければなりません。別のペイロードで以前のキーを再利用した場合は、競合にし、元の操作を暗黙に更新してはなりません。

テスト、可観測性、チェックリスト

テスト、可観測性、チェックリスト — guía visual de DedicatedPHP

ハッピーパス以上をテストしてください。結果を永続化した後に応答を中断し、同じキーを並行して繰り返し、レコード予約後にワーカーを再起動し、外部リクエスト送信後のタイムアウトをシミュレートします。ビジネスリソースが一つだけ存在すること、繰り返した応答が同じ結果を維持すること、同じキーで異なるペイロードが受け入れられないことを検証してください。

機密データを公開せずに、キーまたはそこから導出した安全な識別子、スコープ、状態、相関、外部参照を記録してください。キー競合、長時間保留中の操作、未解決の照合に関するメトリクスは、サポートと運用が通常の再試行と障害を区別する助けになります。

  • キーはビジネス上の意図を表し、定義されたスコープを持っていますか。
  • 二つの同時予約を防ぐ一意制約は存在しますか。
  • ペイロードのハッシュを比較し、意図の変更を拒否していますか。
  • 一貫して繰り返せる応答または結果を永続化していますか。
  • 不確実な状態では、再試行前に照会と照合が可能ですか。
  • 各外部効果に参照、回復、運用上の代替手段がありますか。
  • 重複、障害、キュー再試行、実際の並行性をテストしましたか。

このように適用された冪等性は、ネットワークが信頼できると約束するものではありません。避けられない障害を、ビジネスにとって制御可能で、追跡可能かつ一貫した結果にします。

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