gumadesu

日々の学びをアウトプット

冪等性とはなにか、なぜ必要か

はじめに

StripeやAmazonPayなどの決済API をみていると「冪等性(idempotency)」という概念を見かけます。
なんとなく、参照系じゃなくて更新系のAPIで必要なんでしょ?って思ってるんですが、自信を持って説明できない。
改めて冪等性とはなにか、なぜ必要なのかを理解していく。

TL;DR

  • 冪等性とは「ある操作を1回行っても複数回行っても結果が同じであることをいう概念」
  • 冪等性の導入を検討したほうがよいAPIは、以下のような場合
    • 意図しない複数回の実行を避けたいケース
      • (例)ユーザボタンを連打し、複数回の決済処理が実行される可能性がある
    • ネットワークエラーが発生した場合、クライアント側が処理の成否を正しく判断出来ない場合
      • (例) ネットワークタイムアウトにより1回目の処理結果が受け取れなかったため、2回目のリトライをしてよいか判断しづらい

冪等性とはなにか

wikipedia によると、冪等性とは以下のような意味を持つらしい。

数学において、冪等性(べきとうせい、英: idempotence 「巾等性」とも書くが読み方は同じ)は、大雑把に言って、ある操作を1回行っても複数回行っても結果が同じであることをいう概念である。

もともとは数学の概念なんですね…。 身近な例でわかりやすく例えてみましょう。

冪等性がない例

リモコンの停止/再生ボタン

f:id:kashiwaguma-hiro:20210710152624p:plain

  • 映像が再生中の場合に押すと、停止する。
  • 映像が停止中の場合の押すと、再生する。
  • つまり、ボタンを押すたびに結果が変わる。

冪等性がある例

エレベータの階数ボタン

  • エレベーターに乗る時、7F行きのボタンを1度押すとエレベータを呼ぶことが出来る。
  • 7F行きのボタンを連打しても、早く呼ぶことはできないので押しても効果は変わらない。

バイクのセルスターター

f:id:kashiwaguma-hiro:20210710153249p:plain

  •  エンジンが停止している状態でセルスタータを押すとエンジンがかかる。
  • 既にエンジンが掛かっている状態でセルスターターを押しても、エンジンが止まらないので、何度押しても結果は変わらない。

身近にも冪等性がどうやら潜んでいるらしいとわかりましたね。 でも、なんで冪等性って必要なんでしょうか?

冪等性はなぜ必要?

私の解釈では、冪等性があると「便利だから」だと考えられます。
エレベータの階数ボタンを連打することで早く呼び出せると嬉しいですが、みんながエレベータ連打バトルをやりだしたらどうでしょうか?公平性を欠きそうで、必ずしも便利と言えなさそうですね。
バイクのセルスタータを押すたびにエンジンがついたり消えたりしたら、押し続けるタイミングが非常にシビアになり、圧倒的に不便になりますね。

逆に、冪等性がなくていいものは、冪等性がなくても不便じゃないものだと言えそうです。

APIにおける冪等性

最初の疑問に戻りまして「なぜStripe、AmazonPayのAPIに冪等性があるか」についてですが、これも「あったほうが便利だから」と考えられそうです。 冪等性が必要かどうかはケースバイケースなので、どういう場合に必要か考えてみましょう。

どんなAPIで冪等性が必要?

Stripeのドキュメントを見てみましょう。

べき等

Stripe API は、GET および DELETE リクエストのべき等性を保証しているため、再試行しても安全です。特定量の断続的なエラーが予期されるため、クライアントには、失敗したリクエストをサーバと調整する方法が必要であり、べき等性がそのメカニズムをもたらします。べき等キーを含めると、POST リクエストがべき等性を持ち、API は重複操作を避けるために必要な記録を行います。
 
(中略)
 
たとえば、支払いを作成するリクエストがネットワーク接続エラーのために応答しない場合には、クライアントは、同じべき等キーを使用してリクエストを再試行すれば、重複した支払いが作成されることはありません。

Stripeの場合は HTTP Methodが GET, DELETEの場合は冪等性を保証しているので、冪等性は不要そうですが、 POSTについては冪等性がないため冪等キーによって重複実行を保証 していますね。

HTTP Methodによって、冪等性の要否が変わるのか?

POSTであれば、どんなAPIも冪等性は必要なのでしょうか?
他の例も考えてみましょう。

...

ある会員制サイトではメールアドレスをIDとして利用しており、会員登録のAPIが以下のようなパラメータだとします。

curl POST \
   -H "Content-Type:application/json" \
   -d \
'{
    "name":"kashiwaguma-hiro",
    "email":"sample@example.com",
    "password":"xxxxxxxxxx",
    "password_confirm":"xxxxxxxxxx",
}' \
 'https://api.example.com/v1/register'

会員登録APIを全く同じパラメータで複数回実行する場合、1回目は成功しますが、2回目以降は「すでにemailが登録されています」とエラーで会員登録が失敗しそうです。 このケースだけだと、HTTP MethodがPOSTだからといって冪等性が必要だとは言い切れなさそうです。

ネットワークエラーへの配慮

もしも、ネットワークが不安定な場合はどうでしょうか? 先程の例で、1回目のリクエストでネットワークタイムアウトが発生して結果を受け取れなかったとします。
リトライするために2回目も同じパラメータでリクエストしたら「すでにemailが登録されています」となり会員登録に失敗します。

このとき、クライアント側は「1回目は実は成功していた」か、「本当に別の人が先にメールを登録してしまった」かを区別することが出来ません。

...

冪等性はこのようなケースでも有効に働きます。
冪等性のあるAPIでは、同じ冪等性キーのリクエストが来た場合、1回目と同じレスポンスをするような実装になっています。
そのため、1回目のリクエストでネットワークタイムアウトになりレスポンスを受け取れなくても、2回目に同じ冪等性キーでリクエストすることで1回目の実行結果を参照することができるのです。
何度実行しても、最初のリクエスト結果を参照できるので、クライアント側としては「より便利に扱うこと」ができそうですね。

実は標準化が進んでいる「冪等性」

この冪等性のしくみですが、 RFCにて規格を標準化するため、インターネットドラフトが公開されています。

datatracker.ietf.org

冪等性キーの仕様や、重複したリクエストが来た場合などの対処についても書かれていますので、ご興味があれば見てみてください。

最後に

チームメンバーから「冪等性ってなんで必要なの?」って聞かれても、それとなく説明できるようになった気がします。
また気になることがあれば自己流ですがまとめていこうと思います。