仕組み

冪等性は「べきとうせい」と読みます。HTTPでは、同一のリクエストを複数回送ったとき、サーバーに意図する効果が1回の場合と同じになる性質です。

PUTとDELETEは冪等なメソッドとして定義されています。POSTには、その保証がありません。ここで見るのはレスポンスの一致ではなく、要求した操作の効果です。HTTP仕様:冪等なメソッド

PUTが冪等である理由

PUTは、指定したリソースの状態を、送った内容で作成・置換する操作です。

たとえば、通知設定全体を次のように指定します。

PUT /users/42/notification-settings
Content-Type: application/json

{
  "email": true,
  "push": false
}

同じ要求を繰り返しても、設定は変わりません。

1回目 → email=true、push=false
2回目 → email=true、push=false
3回目 → email=true、push=false

一方、「現在の値を反転する」という処理をPUTに実装すると、呼ぶたびに状態が変わります。PUTと名付けるだけでは冪等にならず、実装もその意味に合わせる必要があります。

DELETEが冪等である理由

DELETE /articles/123

1回目で記事を削除し、2回目も「記事が存在しない」という状態になります。

1回目が 204 No Content、2回目が 404 Not Found でも、冪等性と矛盾しません。レスポンスは違っても、削除という目的は同じ状態に到達しています。

POSTが冪等と保証されない理由

POST /orders
Content-Type: application/json

{
  "productId": "book-1",
  "quantity": 1
}

このAPIが「呼ばれるたびに新しい注文を作る」仕様なら、2回送ると注文が2件できます。

問題になるのは、次の通信障害です。

クライアント → 注文作成を送信
サーバー     → 注文作成に成功
通信        → 応答が途中で失われる
クライアント → 成功したか分からず再送
サーバー     → もう1件作成してしまう

POSTでも重複実行を防げる

代表的な方法が、操作単位の識別子を送る Idempotency-Key です。

POST /orders
Idempotency-Key: 7e24b712-...

サーバー側で、そのキーに対応する実行状態や結果を記録します。同じ操作の再送なら、新しく注文を作らず、既存の結果を返します。Stripeもこの方式を提供しています。Stripe:冪等なリクエスト

実装では、次の条件が重要です。

  • 同じ操作の再送には、同じキーを使う。
  • 別の注文には、新しいキーを使う。
  • 同じキーで異なる内容が来たら、拒否する。
  • 同時に2件届いても、処理を重複開始しないようにする。
  • キーの保存期間と、その期間を過ぎた再送の扱いを決める。

「実行済みか確認してから作成する」だけでは、同時リクエストが両方とも確認を通過できます。DBの一意制約やトランザクションなどで、競合も防ぐ必要があります。

設計上の要点
通信が失敗しても、処理自体は成功している可能性があります。再送を前提に設計することが重要です。