API冪等性設計完全ガイド:冪等キー、分散重複排除と決済安全API
概要
- 冪等性は分散システムの「シートベルト」:ネットワークリトライ、メッセージ重複、ユーザーのダブルクリックでAPIが複数回呼び出される
- 冪等キー(Idempotency Key)はHTTP API冪等の標準手法。Stripe、Alipay、WeChat Payが採用
- 冪等実装の3層防御:クライアント冪等キー → サーバー重複排除テーブル → DB一意制約
- 決済、注文作成、在庫減算は冪等性要求が最も高い3つのAPI — 面接必須
- 本記事では原理からGo実装までの完全ソリューションを提供。Redis + PostgreSQL二重安全アーキテクチャ
目次
冪等性とは何か、なぜ必須か
一言定義
同じリクエストを何度実行しても、1回実行した場合と同じ結果になり、余分な副作用がない。
数式:f(f(x)) = f(x)
なぜ分散システムでは必ず重複リクエストが発生するか
| 重複ソース | 典型シナリオ | 発生頻度 |
|---|---|---|
| ユーザーの重複クリック | 決済ボタン、注文送信 | 毎日発生 |
| ネットワークタイムアウトリトライ | クライアント自動リトライ、ゲートウェイリトライ | 本番環境の常態 |
| メッセージキュー重複配信 | Kafka at-least-once、RabbitMQ再配信 | 設計上そうなっている |
| ロードバランサーリトライ | Nginx/Envoy上流タイムアウトリトライ | 設定ミス時に頻発 |
| マイクロサービスチェーンリトライ | 下流タイムアウト、上流再送信 | チェーンが長いほど多い |
冪等性なしの実際の代償
2025年、ある中古取引プラットフォームで「重複課金」事故:ユーザーが決済クリック後ネットワークが不安定になり、クライアントが自動的に3回リトライ。サーバーに冪等チェックがなく、同一注文で3回課金。200万元以上が関与し、規制当局が調査に介入。
冪等性は最適化項目ではなく、決済/取引システムの参入要件です。
冪等性の4段階
Level 0:自然冪等(HTTP GET/PUT/DELETE)
POSTとPATCHはデフォルトで非冪等 — 呼び出しごとに新リソース作成や新副作用の可能性。
Level 1:ビジネス層冪等
「この注文は既に作成済み、前回の結果を返す」ロジック。シンプルだが並行時にレースコンディションの脆弱性。
Level 2:冪等キー + 重複排除テーブル
クライアントが一意の冪等キーを送信、サーバーが処理済みかを記録。
Level 3:データベース一意制約(最終防衛線)
CREATE UNIQUE INDEX idx_orders_idempotency_key
ON orders (idempotency_key)
WHERE idempotency_key IS NOT NULL;
本番推奨:Level 2 + Level 3の組み合わせ。
冪等キー設計規範
| ルール | 説明 | 例 |
|---|---|---|
| クライアント生成 | 呼び出し元がリクエスト前にUUID生成 | 550e8400-e29b-41d4-a716-446655440000 |
| 一意性 | 同一ビジネス操作で同じキー使用 | リトライ時は元のキーを再利用 |
| スコープ | 通常 user_id + 操作タイプにバインド | 異なるユーザーは同形式のキー可 |
| 有効期限 | 24-72時間 | 期限切れ後は再利用可(稀) |
| 伝達方法 | HTTP Header | Idempotency-Key: <uuid> |
Stripeの冪等キー規範(業界標準)
キー確認 → 未存在:ビジネスロジック実行、結果保存、201返却 → 存在かつパラメータ同一:キャッシュ結果返却(200) → 存在かつパラメータ異なる:409 Conflict。
3層冪等防御アーキテクチャ
Layer 1:Redis SETNX高速インターセプト
func (s *IdempotencyService) TryAcquire(ctx context.Context, key string, ttl time.Duration) (bool, error) {
redisKey := fmt.Sprintf("idem:%s", key)
acquired, err := s.redis.SetNX(ctx, redisKey, "processing", ttl).Result()
if err != nil {
return false, fmt.Errorf("redis setnx: %w", err)
}
return acquired, nil
}
- SETNX成功 → 初回リクエスト、処理続行
- SETNX失敗 → 重複リクエスト、キャッシュ結果を返却
- TTL 24-72h、Redisメモリリーク防止
Layer 2:冪等記録テーブル
状態遷移:processing → completed / failed
Layer 3:ビジネステーブル一意制約
Layer 1と2が失敗しても(Redisダウン、アプリバグ)、DB一意制約が最後の防衛線。
Go本番向け冪等ミドルウェア実装
func (m *IdempotencyMiddleware) Handle() gin.HandlerFunc {
return func(c *gin.Context) {
key := c.GetHeader("Idempotency-Key")
if key == "" {
c.Next()
return
}
ctx := c.Request.Context()
cached, err := m.getCachedResponse(ctx, key)
if err == nil && cached != nil {
c.JSON(cached.Status, cached.Body)
c.Abort()
return
}
acquired, err := m.redis.SetNX(ctx, "idem:"+key, "1", m.ttl).Result()
if !acquired {
for i := 0; i < 30; i++ {
time.Sleep(100 * time.Millisecond)
cached, err = m.getCachedResponse(ctx, key)
if cached != nil {
c.JSON(cached.Status, cached.Body)
c.Abort()
return
}
}
c.JSON(409, gin.H{"error": "duplicate request in progress"})
c.Abort()
return
}
blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
c.Writer = blw
c.Next()
m.saveResponse(ctx, key, c.Writer.Status(), blw.body.String())
}
}
同一冪等キーの並行リクエスト:SETNX + ポーリング待機(100ms × 30回 = 3秒)がStripe、Alipayなど大手の標準パターン。
決済シナリオ冪等実践
決済フロー
- クライアントがIdempotency-Key(UUID)生成
- POST /api/payments + Header
- 冪等チェック:Redis SETNX → 冪等記録テーブル → 注文状態確認
- DBトランザクション内でビジネス処理
- 冪等記録テーブル + Redisにレスポンスキャッシュ
- 結果返却
3つの特別考慮事項
- 決済チャネル側も冪等:WeChat/Alipayにmerchant order number(out_trade_no)必須
- ステートマシン:success後のリトライは常に成功を返す
- 照合フォールバック:冪等は100%信頼できない、日次照合が最後の安全網
面接頻出ポイントと落とし穴ガイド
Q1:GETは冪等、POSTはなぜ非冪等?
GETは読み取り、状態変更なし。POSTは書き込み、呼び出しごとに新リソース作成の可能性。冪等キーでPOSTを冪等にできる。
Q2:Redisダウン時も冪等は有効?
Layer 2(冪等記録テーブル)とLayer 3(一意制約)は有効。Redisはパフォーマンス層。
Q3:分散ロックで冪等を実現できる?
可能だが最適ではない。ロックは相互排他、冪等キー+重複排除テーブルの方がシンプルで信頼性が高い。
Q4:冪等とトランザクションの関係は?
冪等は「複数回実行=1回実行」を保証。トランザクションは「全成功か全失敗」を保証。決済では両方必要。
落とし穴チェックリスト
| 落とし穴 | 症状 | 対策 |
|---|---|---|
| サーバー側キー生成 | リトライ時にキーが異なる | クライアント生成・再利用必須 |
| 成功結果のみキャッシュ | 失敗リトライで古いエラー返却 | 成功・失敗両方キャッシュ |
| TTL未設定 | データ無限増加 | TTL 24-72h + 定期クリーンアップ |
| 並行時ロックなし | 2リクエストが同時にチェック通過 | SETNX + ポーリング待機 |
| 決済チャネル冪等無視 | ローカル冪等だがチャネルで2回課金 | グローバル一意のmerchant order ID |
在庫減算シナリオの冪等設計
決済以外で最重要なのが在庫減算。冪等キー + 条件付き更新(WHERE quantity >= $1)でDB層のアトミックガードにより overselling を防止。
分散環境での冪等性チャレンジ
マルチインスタンスは SETNX + ポーリング待機。Redis フェイルオーバー時は DB 一意制約が最終防御。
パフォーマンス最適化と企業導入事例
三層構成で P99 を最適化。ある決済プラットフォームは導入後8ヶ月間二重課金ゼロ。
ハンズオンと2026年トレンド
30分で冪等決済 API を構築。トレンド:ゲートウェイ標準化、OpenTelemetry 統合、AI Agent API 層冪等性。
まとめと関連記事
核心公式を覚えて:クライアント冪等キー + Redis SETNX + DB一意制約 = 本番級冪等
関連記事:
参考資料:
ブラウザローカルツールを無料で試す →