Idempotency-Key Header に関する調査と実装を半年ぐらい前に行ったので、そのとき参考にしたリソースと 2021 年 9 月時点で得られる最新情報をメモしておく。
前提: Idempotency-Key Header とは
HTTP リクエストのうち冪等ではないとされるリクエスト*1を冪等にし、安全なリトライを可能にするための仕組みの 1 つ。
Jayadeba Jena, Sanjay Dalal, Erik Wilde 氏らによって 2021 年 11 月に仕様が提案された。現在は IETF のもと、インターネット標準化過程(Standard Track)にあり、IETF Meeting や GitHub issues にて議論が行われている。real world ではすでに多くの企業で類似する実装が行われている。
現状と今後
GitHub issues を見る限り論点はいくつか残っており、RFC になるにはまだ時間がかかりそう。
また、de facto と言えるほどサーバ側の実装パターンはまだ固まっていない印象。実装する際には後述する Airbnb や Stripe の記事などを参照しつつ、各実装者が多少なり手心を加えて設計をしなければならなさそうだ。
今後の最新議論を追いかけるなら GitHub issues や IETF meeting をウォッチすることになると思う。
ここからリソースまとめ。
仕様を説明するもの
The Idempotency-Key HTTP Header Field
https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header-00
2021-07-01 に提出された最新の draft。IETF HTTPAPI WG によって accept されたもの。 ちなみに初出は 2020-11-17*2。
解決したい課題、API サーバおよびクライアントに期待する振る舞い、先行する実装等々、全体像を掴むのに十分な情報がある。
POST リクエストを冪等処理可能にする Idempotency-Key ヘッダの提案仕様
https://asnokaze.hatenablog.com/entry/2020/11/19/015243
2020 年 11 月、初期の draft 公開を受けての記事。日本語でわかりやすい簡潔な説明。
2021 年 9 月時点では日本語で "dempotency key header" を検索したときに最上位に来るのが当記事。
ちなみに、そのあと英語の記事がいくつか続いた後に僕の Scrapbox のページが来るぐらいに情報が少ない。英語で検索しよう。
2021-09 時点での最新議論
IETF 111 Meeting の発表資料
https://datatracker.ietf.org/meeting/111/materials/slides-111-httpapi-idempotency-header-00
2021-07-26 の IETF 111 Meeting 資料。現在のステータスと議論の状況をざっと掴める。
Discussion on GitHub
https://github.com/ietf-wg-httpapi/idempotency
draft を受けての各界からの反応・意見・課題について議論している repository。
いくつかななめ読みしてみる。
#2 Clarification for status code for various scenarios
各シナリオで返す status code や命名について。「英語が母国語じゃない自分としては〜」のような意見が寄せられるのも面白い。
#3 Feedback from Google Standard Payments
protocol agnostic な実装であるべきでは?と Googler からの指摘。個人の意見として書いているようだが、gRPC を推す Google 側の position talk と見られる節はある。
#5 How does this header compare with OASIS Repeatable Requests Header?
類似する実装として OASIS (Organization for the Advancement of Structured Information Standards) によるRepeatable Requests Headerが存在するがどう使い分けるか、歩調を合わせるか、はたまた統合の道を辿るか。
各種 SaaS の先行実装
IETF なのですでに実装 (Running code) がある*3。
draft の "4. Implementation Status" に記されているものを参照するとよい。
https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header-00#section-4
主に決済・金融系の SaaS が公開する API で実装されている。先行実装にはIdempotency-Key
の名前を使うものもあれば、Request-Id
と呼んでいるものもある。
SaaS で実装されている、いくつか代表的なものを挙げる。
Stripe
https://stripe.com/docs/api/idempotent_requests
動画もあった。
https://www.youtube.com/watch?v=nnMqSQtSZUQ
各言語の client 実装があるが、いずれも利用者が Idempotency Key のことを知らなくても自動でセットするようになっている。知らなくても安全にリトライできる良い設計と思う。
もちろん、key のことをよく知る実装者であれば form 入力値などによって「一意性」を決定し、key を生成できる。
以下は Ruby client の例。
https://github.com/stripe/stripe-ruby/blob/v5.38.0/lib/stripe/stripe_client.rb#L869-L873
# It is only safe to retry network failures on post and delete # requests if we add an Idempotency-Key header if %i[post delete].include?(method) && config.max_network_retries > 0 headers["Idempotency-Key"] ||= SecureRandom.uuid end
Amazon Pay
https://developer.amazon.com/ja/docs/amazon-pay-api-v2/idempotency.html
日本語で読める。
Note: 冪等キーは各 Amazon Pay 事業者アカウントに固有のものであり、無期限に保存されます。これは、同じキーを異なる事業者アカウントに使用できることを意味します。
key が無期限に保存されるタイプ。
draft によれば、expiry を設定するかどうかは任意。
2.3. Idempotency Key Validity and Expiry
The resource MAY enforce time based idempotency keys, thus, be able to purge or delete a key upon its expiry. The resource server SHOULD define such expiration policy and publish in related documentation.
PayPal
https://developer.paypal.com/docs/business/develop/idempotency/
Idempotency-Key
header の draft を書いたうちの 1 人が PayPal のメンバーなのだが、PayPal 自身はPayPal-Request-Id
header を使っているというのが興味深いポイント。PayPalには"歴史"がありそうだ。
実装するときに参考になるリソース
各種 SaaS の仕様は参考になるが、当然ながら、記されているのは外から見た振る舞いの定義のみ。
仕組み自体はシンプルなので draft を読み込めばコア部分はわりとすんなり実装できるものの、細かい点において迷うポイントがあった。key を保存するストレージに何を選ぶか、エラーシナリオで返す status code は何か、など。
いざ自分で実装してみようと思ったときに参考になる記事をいくつか。
Designing robust and predictable APIs with idempotency
https://stripe.com/blog/idempotency
2017 年、@brandur が Stripe 在籍時に書いた記事。分散システムにおいてなぜ idempotent な HTTP リクエストが重要か、といった基本思想のおさらい。
加えて、クライアントと API を設計する際に従うべきいくつかの基本原則。
- 障害が一貫して処理されるようにする - クライアントにリモートサービスに対する操作を再試行させる。そうしないと、データが一貫性のない状態のままになってしまい、将来的に問題が発生する可能性がある
- 失敗が安全に処理されるようにする - idempotency および idempotency key を使用して、クライアントが一意の値を渡し、必要に応じてリクエストを再試行できるようにすうる
- 失敗が責任を持って処理されることを確認する
- Exponential Backoff や jitter を使って、立ち往生している可能性のあるサーバに配慮する
先述した Stripe の Ruby client の実装を読むと Exponential Backoff や jitter のお手本が見られる。
Implementing Stripe-like Idempotency Keys in Postgres
https://brandur.org/idempotency-keys
これも 2017 年の@brandur の記事。
記事はとても長いのだが Sinatra で書かれた実装が公開されており、この実装をベースに解説しているので非常に参考になる。
https://github.com/brandur/rocket-rides-atomic
同実装では key のストレージとして RDBMS を使用している。これは key の保存とリソースの状態の更新が atomic でなければならないため。リクエスト単位で atomic なのではなく、1 リクエストの中で atomic な処理が複数ある複雑なケースではそういうこともある。
また、リクエストハンドラの中だけでなく idempotency key の reap (expire したレコードの削除) や completer (中途半端に終わったリクエストをあとで非同期に完了させる) の実装も参考になる。
Patterns of Service-oriented Architecture: Idempotency Key
https://multithreaded.stitchfix.com/blog/2017/06/26/patterns-of-soa-idempotency-key/
2017 年に concept を解説している記事。トレーサビリティへの言及が良い。
- とある idempotency key が既存のトランザクションを見つけるために使用されたログを記録しておく
- サービスがいつ既存のレコードを見つけたのか知ることができる
- 不適切に設計された idempotency key algorithm を使っていることを発見できる
Good diagram.
Avoiding Double Payments in a Distributed Payments System
2019 年の Airbnb による記事。分散システムに結果整合をもたらすテクニックのうち、write repair を実現するのが Idempotency-Key である、という導入から始まる。
Airbnb が実装している Orpheus と呼ばれる Java library と設計の基本理念が参考になる。API call を3つのフェーズ(Pre-RPC, RPC, Post-RPC)に分けて考え、API が DB に書き込むのは Pre, Post-RPC フェーズのみとし、APIがさらに downstream や external services (payment processor や bank) にリクエストする部分を RPC とするあたりなど。
他にも、クライアントのリトライや Idempotency-Key の生成方法、key を DB から読み出すときは必ず master を参照する、などのテクニックが詳説されている。
Idempotency-Key
IETF standards draft
https://brandur.org/fragments/idempotency-key-draft
2021 年 7 月、 https://news.ycombinator.com/item?id=27729610 を見ての@brandur の記事なので比較的新しい。HackerNews と合わせて読むと面白い。
Googler のコメントでは「ヘッダーではなくペイロードに入れたほうが良い」と主張があり、これは gRPC との相性のためと@brandur は見ている。
@brandur は「リクエストボディと分離しておくことで、クライアントが意識せずに idempotency を実現できる」と反論している。
実際に Stripe の API クライアントはそのように実装されているので説得力がある。
その他
国内ではメルペイから数多くの知見が公開されている。
- 決済システムのマイクロサービス化に伴うデータ移行について | メルカリエンジニアリング
- メルペイにおけるお客さま残高の管理手法 | メルカリエンジニアリング
- メルペイのマイクロサービスアーキテクチャの裏側と、不整合を防ぐための工夫 - ログミーTech
- アプリケーションにおけるデータ不整合との戦い - blog.syfm
- Merpay Tech Fest 2021 Day1 (7/26): Keynote, Backend and Community Activities - YouTube
- 『マイクロサービスアーキテクチャ』
- 直接的に Idempotency-Key の話はしていないが "11.6 冪等性" に少しだけ類似実装の言及あり
- https://e34.fm/4/
- IETF draft について若干の言及
- サーバーレスにおけるべき等性の実装 (バッチ処理と分散トランザクション編) ~サーバーレスが気になる開発者に捧ぐ「べき等性」ことはじめ 第 4 回~ - builders.flash☆ - 変化を求めるデベロッパーを応援するウェブマガジン | AWS
- 若干毛色が違うものの、AWS Lambda Powertools Python による冪等性の実装
- リトライと冪等性のデザインパターン - Blog by Sadayuki Furuhashi
- Idempotency-Key headerという1ソリューション以前の基本的な理解を助ける記事
各リソースを再訪して細かくコメントしていたら本記事が思ったより長くなってしまった。全体をもう少し整理したら薄い本にできるような気もする。
*1:https://datatracker.ietf.org/doc/html/rfc7231 , https://developer.mozilla.org/en-US/docs/Glossary/Idempotent あたり参照
*2:https://datatracker.ietf.org/doc/html/draft-idempotency-header-01
*3:IETF の仕様策定プロセスではラフコンセンサス。仕様より先に複数の独立した実装があることがほとんど。 https://www.nic.ad.jp/ja/tech/ietf/section4.html