BLOG DEV

冪等性 (Idempotency) — API 設計の基礎

2026-06-15 1 MIN READ
設計 API バックエンド 分散システム
みやざき かいと
KAITO MIYAZAKI

はじめに

ネットワーク障害、タイムアウト、ユーザーの誤操作——現代のソフトウェアはあらゆる場所で「失敗」と向き合っている。失敗した操作はリトライされる。そのリトライが安全かどうかを保証するのが、冪等性 (Idempotency) だ。

「同じ操作を何度繰り返しても、システムの状態が同じになる性質」。これが冪等性の核心であり、分散システムにおける安全設計の基盤となる概念だ。


冪等性とは

定義

同一の操作を 1 回実行しても N 回実行しても、システムの状態が同じになる性質のこと。

数学では f(f(x)) = f(x) と表現される。同じ関数を 2 回適用しても結果が変わらないという性質だ。絶対値関数 |x| がわかりやすい例で、||x|| = |x| が成り立つ。

冪等 vs 非冪等

操作冪等?理由
GET /users/1読み取りのみで状態変化なし
PUT /users/1 (同一ボディ)上書きなので結果は常に同じ
POST /orders (新規作成)×呼ぶたびに注文が増える
DELETE /items/11 回目は削除、2 回目は 404 (状態は一致する)

HTTP メソッドと冪等性 (RFC 7231)

RFC 7231 では、GET, HEAD, PUT, DELETE を冪等なメソッドとして定義している。POST は冪等ではない。これがなぜ重要かというと、プロキシやクライアントが「このメソッドは安全にリトライできる」と判断する根拠になるからだ。


どういったアプリケーションで必要か

ECサイトの決済

ユーザーが「購入」ボタンを二重タップしたとき、2 回課金されてはならない。フロントエンドだけでガードしても、ネットワーク遅延によって同じリクエストが 2 回サーバーへ届くケースは排除できない。

送金サービス (銀行/フィンテック)

タイムアウト後にクライアントがリトライしても、二重送金を防ぐ必要がある。「送金されたかどうか不明」な状態でリトライするのは避けられないため、サーバー側での冪等性保証が必須だ。

マイクロサービス間の非同期通信

Amazon SQS や Google Cloud Pub/Sub は "at-least-once delivery" が基本仕様だ。同一メッセージが複数回配信されることを前提に、コンシューマー側を設計しなければならない。

外部 Webhook の受信

Stripe, GitHub, Slack など外部サービスからの Webhook は、配送保証のために同じイベントを複数回 push してくることがある。受信側での重複排除が必要だ。

共通点: 通信が失敗してリトライされる可能性がある、あるいはユーザーが誤って同一操作を繰り返す可能性がある場面では、冪等性の設計が必須となる。


設計: 決済 API の冪等キー設計

最もシンプルかつ実績のある実装手法が 冪等キー (Idempotency Key) パターン だ。

パターンの概要

  1. クライアントがリクエストごとにユニークな Idempotency-Key ヘッダを生成して送付する (例: UUID v4)
  2. サーバーはそのキーをキャッシュ/DB に保存し、同一キーの 2 回目以降のリクエストには保存済みのレスポンスをそのまま返す

フロー

クライアント                  サーバー
   │                            │
   │─── POST /payments ────────>│
   │    Idempotency-Key: abc123  │
   │                            │─── DB に abc123 存在チェック
   │                            │    存在しない → status: in_progress で INSERT (UNIQUE 制約)
   │                            │    → 決済処理実行
   │                            │    → status: completed + レスポンス保存 に UPDATE
   │<── 200 OK (決済完了) ──────│
   │                            │
   │  (タイムアウト・ネット障害)  │
   │                            │
   │─── POST /payments ────────>│  ← 同一キーで再送
   │    Idempotency-Key: abc123  │
   │                            │─── DB に abc123 存在チェック
   │                            │    completed → 保存済みレスポンスを返す
   │<── 200 OK (決済完了) ──────│  ← 二重課金なし

実装のポイント


よくある注意点

注意点 1: 処理中 (In-Flight) のレースコンディション

「1 回目が DB に保存する前に 2 回目が届く」パターンが最も厄介だ。

単純な「存在チェック → 処理 → 保存」では、チェックと保存の間に割り込まれると 2 重実行が発生する。

対策: キーを受け付けた瞬間に status: in_progress で INSERT し、処理完了後に completed へ UPDATE する。

これにより「先着 1 件だけが処理に進める」構造をアトミックに実現できる。

注意点 2: 同一キーで異なるペイロードが送られてくるケース

クライアントのバグにより「同じ Idempotency-Key で金額だけ違うリクエスト」が来ることがある。

ナイーブな実装では 1 回目のリクエスト結果をそのまま返すため、金額の不一致が気づかれない。

対策: キー保存時にリクエストボディのハッシュ値 (SHA-256 など) も一緒に保存し、再リクエスト時にハッシュを比較する。


設計時の検討ポイント

1. キー生成はクライアント責任にする

サーバーがキーを生成してしまうと、初回リクエスト自体の重複を防げない。

フロントエンド側のライフサイクル設計が重要だ。フォーム画面がマウントされたタイミングで UUID を生成し、決済完了まで画面の State に保持し続ける。画面をリロード・閉じると新しい UUID になり、自然に「別リクエスト」として扱われる。

「ボタンを押したときに UUID を生成」すると二重クリックで 2 つの UUID が生まれてしまうため、マウント時生成が正しい設計だ。

2. レスポンスを丸ごとキャッシュする

処理結果だけでなく HTTP ステータスコードも保存し、リトライ時に完全一致のレスポンスを返す。ステータスコードが変わると、クライアントの挙動が変わってしまう。

3. 部分成功状態 (Partial Failure) を作らない

DB への書き込みとキャッシュ保存をトランザクションで束ねる。途中で失敗するとキーが汚染され、以降のリトライが壊れたレスポンスを返し続ける。

4. 副作用を持つ処理に注意する

メール送信・プッシュ通知は処理の一部として発火させると重複しやすい。「送信済み」フラグを DB に持ち、送信前に必ずチェックする。

5. 冪等性とリトライ戦略はセットで設計する

Exponential Backoff + Jitter をリトライ側に実装することで、サーバー側の負荷集中を防ぐ。冪等性だけ担保してリトライが集中すると、障害時にサーバーがダウンする。

6. 自動テストで担保する

同一キーで 2 回リクエストを送り、副作用 (課金・DB レコード) が 1 回分しか発生しないことを自動テストで確認する。「たぶん大丈夫」では不十分で、テストで明示的に保証することが重要だ。


まとめ

冪等性は「失敗を前提とした設計」の中心的な概念だ。

次のアクションとして、自分のプロジェクトの POST エンドポイントを洗い出し、冪等性が担保されているかを確認してみてほしい。


参考

Related articles
  • 2026.05.30 · 1 MIN
    はじめての投稿