コンピュータサイエンス

冪等性とは?決済ボタンを2回押しても安全な理由

冪等性とは、同じリクエストを何度送っても結果が同じになる性質です。ネットワーク障害とリトライで必要な理由、GET・PUT・DELETEとPOSTの違い、Idempotency-Keyの実装まで整理します。

読了 5 分
冪等性とは?決済ボタンを2回押しても安全な理由のカバー画像

決済ボタンを押したのに、画面がしばらく固まったままだと不安になります。もう一度押したくなりますよね。

でも、2回押したら2回決済されてしまうのでは?

よく設計されたシステムなら、そうはなりません。それを保証する性質には名前があります。

冪等性です。

バックエンドのドキュメント、HTTP仕様、決済APIガイドで必ず登場する概念ですが、定義は1行で済みます。難しいのは「なぜこれほど重要なのか」と「どう実装するのか」です。

この記事では、その2つを説明します。

要点をまとめます。

  1. 冪等性:同じリクエストを1回送っても何度送っても結果が同じになる性質
  2. 重要な理由:ネットワークは失敗し、失敗したらリトライが必要で、リトライを安全にするには冪等でなければならない
  3. HTTPではGET・PUT・DELETEが冪等で、POSTは冪等ではない
  4. POSTを安全にする実務上の仕組みがIdempotency-Key

定義:何度行っても1回行ったのと同じ

冪等性は数学に由来する言葉です。ある演算fがf(f(x)) = f(x)を満たすとき、冪等であるといいます。

絶対値関数がよい例です。 |−5| = 5で、 |5| = 5です。

1回適用しても100回適用しても、結果は同じです。

APIに適用すると、次のようになります。

同じリクエストを1回送ってもN回送っても、サーバーの状態は同じです。

注意点は、レスポンスが同じでなければならないという意味ではなく、サーバーの状態が同じでなければならないということです。2回目のDELETEリクエストが404を返しても、「そのリソースが存在しない」というサーバー状態は同じなので冪等です。

日常の例ならエレベーターのボタンです。5階のボタンを5回押しても、エレベーターは5階へ1回行くだけです。

一方、「5階上へ移動」するボタンなら、押すたびに結果が変わります。前者が冪等、後者が非冪等です。


なぜ重要か:ネットワークは必ず失敗する

冪等性が重要な理由は、1つのシナリオで説明できます。

クライアントが決済リクエストを送信し、サーバーが決済を処理しました。

しかし、レスポンスが戻る途中でネットワークが切断されました。

クライアントには、リクエストがサーバーに届く前に失敗したのか(決済されていない)、処理後にレスポンスだけが失われたのか(決済済み)を知る方法がありません。

どちらの場合も、結果は同じタイムアウトに見えるからです。

同じ冪等性キーでリトライしても決済が1回だけ処理されるシーケンス図
レスポンスが失われると、成功したかどうか分かりません。だから冪等性が必要です。

選択肢は2つだけです。リトライを諦めるか(決済されていないかもしれない)、リトライするか(済んでいれば二重決済になる)。どちらも困ります。

冪等性がこのジレンマをなくします。リクエストが冪等なら、「分からなければもう一度送る」が常に安全な戦略になります。

自動リトライ、メッセージキューのat-least-once配信、クライアントでの連打による更新も、すべて恐れる必要がなくなります。

分散システムの設計書で「リトライは冪等な処理にだけ設定する」と繰り返し書かれる理由です。


HTTPメソッドで見る冪等性

HTTP仕様(RFC 9110)では、メソッドごとの冪等性が定義されています。

メソッド 冪等? 理由
GET O 参照は状態を変更しない
PUT O 「この値に置き換える」は何度行っても同じ値
DELETE O 「削除する」は何度行っても削除済みの状態
POST X 「新しく作る」は呼ぶたびに1つずつ増える
PATCH 条件付き 「このフィールドをXにする」は冪等、「1加える」は非冪等

PUTとPOSTの対比が重要です。PUTは「結果の状態」を指定するため冪等で、POSTは「行為」を指示するため非冪等です。

PATCHは内容によって異なります。

ブラウザで戻るボタンからPOSTページに戻ったとき、「フォームを再送信しますか?」と警告されるのも、POSTの再送信が安全でないとブラウザが知っているからです。


実務上の仕組み、冪等性キー

しかし、会員登録、注文作成、決済は本質的にPOSTです。非冪等な処理を避けることはできません。

そこで、非冪等なリクエストを冪等にする仕組みが登場しました。Idempotency-Keyです。

動作は単純です。

  1. クライアントはリクエストごとに一意のキー(通常はUUID)を生成し、ヘッダーに入れて送信する
  2. サーバーは初めて見るキーなら通常どおり処理し、キーとともにレスポンスを保存する
  3. 同じキーで再び届いたら処理せず、保存済みのレスポンスをそのまま返す

リトライでもダブルクリックでも、同じキーである限りサーバーは1回だけ処理します。StripeやToss Paymentsなどの決済APIが実際にこのヘッダーをサポートしており、決済連携ガイドでも必須項目として扱われています。

クライアント側の細かな注意点が1つあります。「リトライ時は同じキーを使う」ことです。

リトライごとに新しいキーを作ると、サーバーには別のリクエストに見えるからです。キーは「同じ意図のリクエスト」単位で作成します。

同じキーが付いた2つの封筒のうち1つだけを処理し、レシートを1枚だけ出す決済端末
同じ冪等性キーで届いた場合、サーバーは1回だけ処理し、保存済みのレスポンスを返します。

まとめ

  • 冪等性とは、同じリクエストを何度送ってもサーバー状態が同じになる性質です(f(f(x)) = f(x))
  • ネットワーク障害で「成功したか分からない」状況では、冪等であって初めてリトライが安全になります
  • HTTP:GET・PUT・DELETEは冪等、POSTは非冪等、PATCHは内容によって異なる
  • PUTは結果の状態を指定するため冪等、POSTは行為を指示するため非冪等
  • 非冪等なPOSTはIdempotency-Keyで冪等にする — 同じキーなら1回だけ処理
  • リトライ時は必ず同じキーを再利用します。キーは「同じ意図のリクエスト」単位です