一人前エンジニア道場

バックエンド開発 / 約25分

冪等性: 二重送信・リトライに耐える設計

なぜこれを学ぶか

ネットワークは不安定です。ユーザーは連打します。決済APIが「2回呼ばれたら2回課金される」設計だと、事故になります。

冪等性(べきとうせい) とは「同じ操作を何度実行しても結果が同じ」になる性質です。GETやDELETEは本来冪等ですが、POSTは冪等ではありません。

解決策の代表例が 冪等キー(Idempotency Key) です。クライアントが一意のキーを付けて送信し、サーバーは「このキーは処理済み」を記録して、2回目以降は最初の結果を返します。Stripeなどの決済APIはすべてこの方式です。

データベースレベルでは ユニーク制約 が最後の砦です。「アプリ側でチェックしてからINSERT」はレースコンディションで必ずすり抜けます。制約に頼りましょう。

「支払い」「注文」「予約」など、重複が許されない処理を見つけたら、冪等性の設計を必ず問う癖をつけましょう。

重要ポイント

  • 冪等性 = 何度実行しても同じ結果
  • 冪等キーでリトライを安全にする
  • ユニーク制約がレースコンディションの最後の砦

コードレビュー演習

後輩が書いた注文APIのコードをレビューしています。決済を含む重要なエンドポイントです。

app.post('/api/orders', async (c) => {
  const { userId, itemId } = await c.req.json()

  // 在庫チェック
  const item = await db.getItem(itemId)
  if (item.stock <= 0) {
    return c.json({ error: 'out of stock' }, 400)
  }

  // 在庫を減らす
  await db.updateItem(itemId, { stock: item.stock - 1 })

  // 注文作成
  const order = await db.createOrder({ userId, itemId })

  return c.json(order, 201)
})

このコードの最も危険な問題は?

ヒントを見る

2人のユーザーが「在庫1」の商品に同時に注文したら何が起きるか、コードを1行ずつ頭の中で実行してみましょう。チェックと更新の間に別のリクエストが割り込めないかがポイントです。

コーディング演習実際に書く

冪等キーを使った決済処理の判定関数を実装してください。processedKeys(処理済み冪等キーの配列)と今回のキーを受け取り、処理済みなら 'duplicate'、未処理なら 'charged' を返してください。

function checkPayment(processedKeys, key)

スキルセルフチェック

このレッスンの内容をどの程度理解できましたか?