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:冪等記録テーブル

状態遷移:processingcompleted / 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など大手の標準パターン。


決済シナリオ冪等実践

決済フロー

  1. クライアントがIdempotency-Key(UUID)生成
  2. POST /api/payments + Header
  3. 冪等チェック:Redis SETNX → 冪等記録テーブル → 注文状態確認
  4. DBトランザクション内でビジネス処理
  5. 冪等記録テーブル + Redisにレスポンスキャッシュ
  6. 結果返却

3つの特別考慮事項

  1. 決済チャネル側も冪等:WeChat/Alipayにmerchant order number(out_trade_no)必須
  2. ステートマシン:success後のリトライは常に成功を返す
  3. 照合フォールバック:冪等は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一意制約 = 本番級冪等

関連記事

参考資料

ブラウザローカルツールを無料で試す →

#API幂等性#分布式幂等#Idempotency Key#支付接口设计#面试高频#2026