冪等性(べきとうせい・idempotency)とは、「同じ操作を1回実行しても、複数回実行しても、結果が同じになる」性質のことです。たとえば「ユーザーのステータスを"有効"にする」操作は、何度繰り返しても結果は"有効"のまま——これが冪等な操作です。一方「残高から1,000円引く」操作は、繰り返すたびに残高が減るので冪等ではありません。

なぜこの性質が重要なのか。ネットワーク越しの通信では「1回だけ届ける」ことを保証できないからです。この記事では、その仕組みから、二重課金が起きる原因、冪等キーの実装、リトライ設計との組み合わせまで、決済・金融システムを手がける株式会社RJCの実務の視点で解説します。

なぜ冪等性が必要なのか:「ちょうど1回」は保証できない

分散システムには、根本的な制約があります。メッセージを「ちょうど1回(exactly once)」届けることは、原理的に保証できません。

送信して応答が返ってこなかったとき、送り手には2つの可能性が区別できません。

  • 相手に届いていない(再送すべき)
  • 相手に届いたが、応答だけが失われた(再送すると重複する)

再送すれば重複の可能性があり、再送しなければ欠落の可能性がある。だから現実の設計は、「最低1回は届ける(at-least-once)」+「受け手が重複を無害化する」という組み合わせになります。この「重複を無害化する」役割を担うのが、冪等性です。

二重課金はこうして起きる

決済APIで典型的な事故の流れです。

  1. クライアントが「1,000円の決済」をリクエスト
  2. サーバーは決済を実行し、DBに記録した
  3. しかし応答がネットワークの途中で失われた
  4. クライアントは「失敗した」と判断し、同じリクエストを再送
  5. サーバーは新しいリクエストとして受け取り、もう一度1,000円を決済

ここに悪意も不具合もありません。タイムアウト+リトライという、ごく正常な動作の組み合わせで二重課金が発生します。リトライを実装するなら、冪等性はセットで必須です。片方だけだと、リトライが二重課金の製造機になります。

HTTPメソッドと冪等性

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

メソッド 冪等 理由
GET ○ 取得は何度しても状態が変わらない
PUT ○ 「この内容で置き換える」は何度しても同じ結果
DELETE ○ 「削除済み」は何度削除しても削除済み(2回目が404でも状態は同じ)
POST × 「新規作成」「加算」は繰り返すと結果が変わる
PATCH 内容次第 「値をXにする」は冪等、「値に1足す」は非冪等

問題になるのは、決済・注文・登録といったPOSTで表現される操作です。仕様上は非冪等なので、アプリケーション側で冪等性を実装する必要があります。

冪等キー(Idempotency Key)の実装パターン

もっとも広く使われている手法が、冪等キーです。主要な決済サービスのAPIが要求する Idempotency-Key ヘッダは、この仕組みです。

基本の流れ

  1. クライアントがリクエストごとに一意のキー(UUIDなど)を生成し、ヘッダやパラメータに付けて送る
  2. サーバーは処理の前に、そのキーが処理済みかを確認する
  3. 未処理なら実行し、キーと結果を保存して応答を返す
  4. 処理済みなら実行せず、保存しておいた前回の結果をそのまま返す

再送されたリクエストは同じキーを持つので、2回目以降は「前回の答えを返すだけ」になります。これで、クライアントが何度リトライしても、決済は1回しか走りません。

実装で押さえるべき5つのポイント

1. キーはクライアントが生成する サーバーが生成すると、応答が失われたときにクライアントがキーを知り得ません。「リトライ時に同じキーを送れる」ことが本質なので、生成はクライアント側です。

2. 「確認」と「保存」を原子的に行う 「キーを確認→未処理→実行→保存」の間に同じキーの2本目が来ると、両方が「未処理」と判断して二重実行します。DBのユニーク制約でキーの挿入を先に行い、成功した方だけが処理を進める設計にします。

3. 処理中の状態も記録する 「実行中」のキーに2本目が来たら、失敗ではなく「処理中」として待たせるか、同じ結果を後で返します。実行中を「未処理」と扱うと、ここでも二重実行が起きます。

4. 同じキーで内容が違うリクエストは拒否する 同じキーで金額だけ変えたリクエストが来た場合、前回の結果を返すのではなくエラーにします。キーと内容のハッシュを一緒に保存しておくと検出できます。

5. キーには有効期限を設ける 永久に保存すると肥大化します。リトライが起こり得る期間(数時間〜数日)を超えたら削除する運用が一般的です。

冪等キーを使わない冪等化の方法

冪等キーが唯一の手段ではありません。操作の設計自体で冪等にできる場合もあります。

  • 「加算」を「置換」に変える — 「残高を1,000円減らす」ではなく「残高を9,000円にする」と表現する。ただし競合更新には別途対策が必要
  • 業務上の一意キーを使う — 「注文番号ごとに1回しか決済できない」というユニーク制約をDBに置く。注文番号自体が冪等キーの役割を果たす
  • 状態遷移で表現する — 「未払い→支払済み」の遷移として実装し、すでに支払済みなら何もしない

リトライ設計との組み合わせ

冪等性を確保したうえで、リトライ側にも定石があります。

  • 指数バックオフ — 再送の間隔を1秒→2秒→4秒と伸ばす。障害中のサーバーへの再突撃を防ぐ
  • ジッター — 待ち時間にランダムなずれを加える。多数のクライアントが同時に再送する「リトライストーム」を防ぐ
  • リトライ回数の上限 — 無限に再送しない。上限に達したら人間が判断できる形で記録する
  • リトライすべきエラーの選別 — タイムアウトやネットワークエラーは再送、認証エラーや不正な入力は再送しない

「エラー時は3回リトライ」とだけ書かれた設計書を見たら、冪等性・バックオフ・ジッター・対象エラーの4点を確認するのが、レビューの定石です。

冪等性が求められる場面

  • 決済・課金 — 二重課金は信頼を直撃する
  • 注文・予約 — 二重注文、二重予約
  • メール・通知送信 — 同じ通知が複数回届く
  • メッセージキューの消費 — キューは基本的にat-least-once配送なので、消費側の冪等性が前提
  • バッチ処理の再実行 — 途中で落ちたバッチを頭から再実行しても、結果が壊れない設計

よくある質問

Q. GETは冪等なので、リトライは何も考えなくていいですか? A. 取得自体は冪等ですが、「GETのたびに閲覧数を加算する」ような副作用を持たせていると非冪等になります。仕様上の冪等性と、実装上の冪等性は別に確認が必要です。

Q. 冪等キーは、どこに保存すればいいですか? A. 処理本体と同じトランザクションで保存できるDBが最も安全です。別のストア(キャッシュ等)に置く場合は、保存と処理の間の整合性をどう保つかを設計してください。

Q. 冪等性と排他制御(ロック)は何が違いますか? A. 排他制御は「同時に走らせない」仕組み、冪等性は「何度走っても同じ結果にする」性質です。冪等キーの実装では、確認と保存を原子的に行うために排他制御(ユニーク制約など)を使います。両者は組み合わせて使うものです。

Q. 小規模なシステムでも必要ですか? A. 外部APIを呼ぶ、またはキューを使う時点で必要です。規模ではなく「ネットワークを越えるか」で判断してください。

まとめ

冪等性は、「メッセージは重複して届く」という分散システムの現実を受け入れたうえで、正しさを守るための設計です。

  • ネットワーク越しに「ちょうど1回」は保証できない
  • だから「最低1回届ける+受け手が重複を無害化する」
  • 無害化の定番が冪等キーで、確認と保存の原子性が実装の急所
  • リトライは冪等性・バックオフ・ジッター・対象選別とセットで設計する

株式会社RJCが携わる決済や金融のシステムでは、冪等性は「あると良い設計」ではなく「前提」です。外部連携を書く全員に、今日から効く知識としてお役に立てば幸いです。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?