【ITニュース解説】Same idempotency key, different amount, same response
2026年10月07日に「Dev.to」が公開したITニュース「Same idempotency key, different amount, same response」について初心者にもわかりやすく解説しています。
ITニュース概要
支払いシステムで、金額を修正しても古い冪等性キーを再利用したため、古い金額で処理される問題が発生した。ゲートウェイはキーのみで判断し、内容との不一致があってもエラーが出ず、原因特定が困難だった。リクエスト内容が変わる際は、必ず新しいキーを生成すべきだ。
ITニュース解説
システム開発において、特に金銭のやり取りが伴う処理では「べき等性」という概念が非常に重要になる。べき等性とは、同じ操作を何度繰り返しても、その結果が常に同じになることを保証する性質のことだ。例えば、インターネット接続が不安定な時に支払いボタンを複数回クリックしてしまっても、支払いが一度だけ完了し、二重に請求されないようにするための仕組みがこれに当たる。この仕組みを実現するために「べき等性キー」という一意の識別子を使うことが多い。
今回取り上げるニュースは、このべき等性キーの扱いに関する予期せぬ問題と、それが引き起こした混乱についての話だ。ある開発チームが、支払いゲートウェイとの連携で遭遇したバグについて報告している。この問題は、クライアントが支払いリクエストを再送する際に発生した。最初に送ったリクエストが何らかの理由で失敗し、その後、金額を修正してもう一度リクエストを送る必要があったのだが、この時、以前失敗したリクエストで使ったべき等性キーを再利用してしまったのだ。
開発チームの想定では、べき等性キーは「そのリクエストの全ての内容」と紐付いているはずだった。つまり、もしリクエストの内容、特に金額のような重要なパラメータが変更されたのであれば、システムはそれを新しいリクエストとして扱うか、あるいは不一致を検知してエラーを返すはずだと考えていた。しかし、現実は全く異なっていた。支払いゲートウェイは、送られてきたべき等性キーを認識すると、以前にそのキーで処理されたキャッシュされたレスポンスを返してきたのだ。このレスポンスには、当初の金額、当初のトランザクションID、そして「200 OK」という成功を示すステータスコードが含まれていた。システムは金額が変更されたことを全く検知せず、元の(間違った)金額で処理が成功したとみなしてしまったのだ。
この問題の厄介な点は、エラーが発生しなかったことだ。通常であれば、何らかの不一致があればエラーメッセージや警告が返されると期待されるが、今回は「200 OK」という、全てが順調に進んだかのような成功レスポンスが返ってきてしまった。レスポンスの本文にも、金額の不一致を示すようなフラグは一切含まれていなかった。これが、このバグの発見を非常に困難にした要因の一つだ。
開発チームがこの問題に気づくまでに丸一日かかったというのも、納得がいく話だ。なぜなら、支払いが「失敗した」わけではなく、「正しいように見えるが、実際には間違った金額で成功した」という、より検出が難しい症状だったからだ。問題は、システムの「調整ジョブ」によって発見された。調整ジョブとは、注文データと実際の支払いデータを突き合わせて、両者が一致しているかを確認する仕組みのことだ。この調整ジョブが、注文合計と支払い合計の間に不一致があることを検知したことで、ようやく問題が浮上した。そこからは、ログを丹念に調べていくという、地道なデバッグ作業が始まった。「なぜ金額が4200のリクエストが、3800という金額のレスポンスを返しているのか」という疑問を解決するために、膨大なログデータから原因を探し出す必要があったのだ。
最終的に判明した原因は、プロバイダのドキュメントの奥深くに書かれた一文にあった。そこには「べき等性キーはペイロード(リクエスト本文)とは独立して照合される」と明記されていたのだ。開発チームは、べき等性キーがリトライ処理の例として紹介されている部分までは読んでいたものの、その数段落先に書かれたこの重要な記述を見落としていた。つまり、支払いゲートウェイは、べき等性キーが一致すれば、リクエストの内容(金額など)が変わっていても、過去のレスポンスをそのまま返す仕様だったということだ。多くの開発者は、リトライの際には同じキーを再利用しつつ、新しい内容のリクエストであれば新しい処理を期待する。しかし、このプロバイダのシステムはそのように設計されていなかったのだ。
この問題を解決するための当面の修正策は、非常にシンプルだった。リクエスト内のどのフィールドであっても、何か変更があった場合には、ネットワークレベルのリトライ時だけでなく、常に新しいべき等性キーを生成するように変更したのだ。これによって、金額が修正されたリクエストは必ず新しい処理として扱われるようになり、過去のキャッシュされたレスポンスが誤って返されることはなくなった。
しかし、この経験から浮上したのは、より根本的な疑問だ。なぜべき等性のスコープ(キーがどこまでを識別するのか)が、プロバイダ間で一貫してペイロードを考慮する設計になっていないのだろうか、という点だ。一部のプロバイダは、リクエストの本文全体をハッシュ化して、それをべき等性キーのチェックに組み込むことで、リクエスト内容の変更も検出できるようにしている。しかし、他のプロバイダは今回のように、キーのみで判断し、ペイロードは無視する。この不一致が、開発者を混乱させ、今回のような予期せぬバグを引き起こす原因となっている。
この問題に対して、ある開発者は「ゲートウェイの照合ロジックを信頼するのではなく、クライアントサイドでべき等性キーとペイロードの紐付けを強制するラッパーを構築した人はいないだろうか」と問いかけている。これは、プロバイダ側の挙動に依存するのではなく、リクエストを送る側(クライアントサイド)で、べき等性キーがリクエストの内容と常に一致するように管理する仕組みを導入することで、今回のような問題を未然に防ごうとする考え方だ。
今回の事例は、システム開発における「想定」と「現実」のギャップがどれほど大きな問題を引き起こしうるかを示す良い教訓となる。特に、重要なインフラを構成する外部サービスの挙動については、ドキュメントを隅々まで読み込み、自身のシステムがそれにどう依存しているかを正確に理解することが不可欠だ。また、エラーが発生しない「サイレントな失敗」は発見が難しいため、調整処理や監視メカニズムをしっかりと構築し、予期せぬ不一致を早期に検出できる体制を整えることの重要性も示している。べき等性の実装は一見単純に見えるが、その詳細な挙動はシステムによって異なり、それが思わぬ落とし穴になることもある。システムエンジニアを目指す上で、こうした細かいが重要な概念と、それらが実際のシステムでどのように振る舞うかを理解することは、安全で信頼性の高いシステムを構築するために不可欠なスキルとなるだろう。