Idempotency-Key Header
idempotency key を受け取ったサービスはデータベースをチェックして同一コンシューマからのリクエストが、与えられた idempotency キーで以前にサービスされたものかどうかを調べる
idempotency キーが特定のコンシューマにスコープされていることが重要
ohbarye.icon SaaSに限らず、複数のコンシューマがいる場合を想定
既存のレコード/レスポンスが見つかった場合、サービスはそれを返す
見つからなければ、サービスは後で使えるようにconsumer ID/idempotencyキーを保存し、リクエストにサービスを提供しなければならない
この操作はそれ自体がidempotentでなければならない
コンシューマ ID/IDempotency キーを記録しているのに仕事をしない (あるいはその逆) という状況は避けたい
作業が完了すると、新しいレスポンスがコンシューマに返される
key algorithm
コンシューマは他のコンシューマが再現できるkey algorithmを使ってはならない
コンシューマは特定のユースケースに合わせてkeyを設計しなければならない
サービス側もコンシューマ側もkeyから意味を解析する必要はない
とはいえ、難読化しすぎるとデバグが困難になる
ハッシュ化したくなるかもしれないがほとんど価値はない、keyの生成をあとからトレースするのが難しくなる
とあるidempotency keyが既存のトランザクションを見つけるために使用されたログを記録しておく
サービスがいつ既存のレコードを見つけたのか知ることができる
不適切に設計されたidempotency key algorithmを使っていることを発見できる
HTTP のコンテキストで idempotency について話すときに、safetyという言葉が出てくる
HTTP メソッドが安全とは、その HTTP メソッドがサーバーの状態を変更しないということ
下の表は、一般的に使用される HTTP メソッドとその安全性、および idempotency を示す
code:table
Idempotence in HTTP
+-------------------+--------------+-------------------+
| Http Method | Saftey | Idempotency |
+-------------------+--------------+-------------------+
| GET | Yes | Yes |
| PUT | No | Yes |
| POST | No | No |
| DELETE | No | Yes |
| PATCH | No | No |
+-------------------+--------------+-------------------+
APIを実装するときの推奨事項
x-idempotency-keyのようなHTTP headerを使う
事前に決めた時間が経過したらidempotency keyをinvalidateする
経過後のリクエストは新たなリクエストとして扱う
おかしいidempotency keyの扱い方がされていないかを検証する
リトライの際に初回と同一のkeyを使いつつもpayloadが異なるなど
新規リクエストとして扱うか、bad requestとするかは実装次第
Paypalメンバーによるドラフト
実装例
すべてのPOSTリクエストに有効
GET, DELETEにはつけても意味ない
成功したか失敗したかに関わらず、与えられたidempotencyキーに対して最初に行われたリクエストの結果のステータスコードとボディを保存することで動作します。同じキーを使ったそれ以降のリクエストは、500エラーを含む同じ結果を返します。
keyは24時間以上経過すると自動的にシステムから削除され、削除された後に元のkeyが再利用されると新しいリクエストが生成されます。idempotency レイヤーは、誤って誤用されることを防ぐために、入力されたパラメータを元のリクエストのものと比較し、同じものでない限りエラーにします。
結果が保存されるのは、API エンドポイントが実行を開始した場合のみです。入力パラメータが検証に失敗した場合、またはリクエストが同時に実行されている別のリクエストと競合した場合、API エンドポイントが実行を開始しなかったため、結果は保存されません。これらのリクエストを再試行しても安全です。
サーバがIdempotent-Replayed: trueを返すことにより、リプレイ以前に実行されたかどうかを判別できる
key
v4 UUIDなど十分なエントロピーを持つランダムな文字列を使うべき ショッピングカートのIDなどユーザー操作から導出するとよい
失敗による分散状態の不整合に対処する最も簡単な方法は、サーバのエンドポイントを idempotent に実装すること
HTTPのセマンティクスではPUT, DELETEはidempotent
PUTはペイロードによってターゲットリソースが完全に置き換えられる
本質的にidempotentなメソッドはよいが、そうでないメソッドはどうすればよいか
ohbarye.icon この例めっちゃ出てくる、さすがStripe 分散システムにおける障害の可能性とその対処方法を考慮することは、ロバストで予測可能なAPIを構築する上で最も重要
クライアント上でのリトライロジックとサーバー上でのidempotencyは、この目標を達成するために有用なテクニックであり、どのようなテクノロジースタックでも比較的簡単に実装できる
クライアントと API を設計する際に従うべきいくつかの基本原則
障害が一貫して処理されるようにする
クライアントにリモートサービスに対する操作を再試行させる。そうしないと、データが一貫性のない状態のままになってしまい、将来的に問題が発生する可能性がある
失敗が安全に処理されるようにする
idempotency および idempotency key を使用して、クライアントが一意の値を渡し、必要に応じてリクエストを再試行できるようにすうる
失敗が責任を持って処理されることを確認する
methodがPOST or DELETEで、リトライ回数を1以上に設定している場合は自動的にセット
headers["Idempotency-Key"] ||= SecureRandom.uuid
Stripeの実例を紹介
Googlerのコメント
ヘッダーではなくペイロードに入れたほうが良いと主張
これはgRPCとの相性のためとbrandurは見ている brandurは「リクエストボディと分離しておくことで、クライアントが意識せずにidempotencyを実現できる」と反論
実際StripeのAPIクライアントはそのように実装されている
メルペイ
1:14:00あたり
https://www.youtube.com/watch?v=Dq9Erg3SIZk
その他