冪等性
同じ操作を何度実行しても結果が変わらない性質。再送が避けられない分散系で信頼性の要になる。
概要
冪等性(べきとうせい)とは、「同じ操作を1回実行しても、2回実行しても、100回実行しても、結果(状態)が変わらない」という性質のことです。たとえば「照明のスイッチをオフにする」操作は冪等です。何度オフにしても、照明が消えているという結果は同じだからです。一方「照明のスイッチを切り替える」操作は冪等ではありません。実行するたびに点いたり消えたりして、結果が変わってしまいます。
この性質が重要になるのは、API呼び出しやメッセージ処理のように「同じ要求が意図せず複数回届いてしまう」状況です。ネットワーク越しの通信では、リクエストが本当に失敗したのか、成功したのに返事が届かなかっただけなのかを、送り手が確実に見分けることはできません。そのため送り手は「念のためもう一度送る」という再送を行いますが、もし受け手の処理が冪等でなければ、この再送が二重の注文・二重の課金・二重の在庫引き当てといった事故を引き起こします。冪等性は、こうした再送が避けられない世界で信頼性を保つための土台です。
なぜ生まれたか
一台のマシンで完結する処理なら、関数を呼べば成功か失敗かがその場で分かり、「二重に実行された」という心配はほとんどありません。ところがネットワークをまたいだ分散システムでは、通信のどこで何が起きたかを送り手が完全には知りえない、という根本的な不確実性が生まれます。
決済リクエストを送ったのに応答が返ってこない場面を考えてみましょう。原因は二通りあります。ひとつは「サーバに届く前に要求が消えた」場合で、このときは再送すべきです。もうひとつは「サーバは決済を完了させたが、その成功応答が途中で消えた」場合で、このときに再送すると二重決済になります。送り手には、この二つを区別する手立てがありません。「返事がないなら再送する」という自然な戦略と、「二重処理は絶対に避けたい」という要求が、真正面からぶつかるのです。この矛盾を解く鍵として、「何度届いても結果が一つに収束するなら、安心して再送できる」という発想、すなわち冪等性が設計原則として重視されるようになりました。再送を恐れるのではなく、再送されても壊れない受け手を作る、という方向への転換です。
詳細
HTTPメソッドに刻まれた冪等性
冪等性は、実はHTTPの仕様にも組み込まれた基本概念です。GET・PUT・DELETE は「冪等であるべき」と定められています。GET は状態を変えない読み取りなので当然冪等です。PUT は「このリソースを、この内容にする」という置き換えなので、同じ内容で何度実行しても結果は同じです。DELETE も「このリソースを消す」なので、二度目以降は「すでに無い」だけで、消えているという結果は変わりません。
問題児は POST です。POST は「新しいリソースを作る」「注文を確定する」といった、実行するたびに新しい状態を生む操作に使われるため、冪等ではありません。REST APIを設計する際、更新を PUT で表現できる場面ではあえて PUT を選ぶことで、再送に強い API になります。裏を返せば、POST を使わざるをえない「新規作成・課金」のような処理でこそ、冪等性を別の仕組みで作り込む必要が出てきます。
冪等キーで二重処理を防ぐ
冪等でない POST を安全にする定石が「冪等キー(idempotency key)」です。クライアントが「この要求を一意に識別する使い捨ての鍵」を生成してリクエストに添え、サーバは「同じ鍵の要求は一度しか処理しない」ことを保証します。決済 API などでは、リクエストヘッダに Idempotency-Key を載せる形で広く使われています。
流れを追ってみます。ポイントは、サーバが鍵ごとに「処理結果」を記録しておき、二度目以降は処理をやり直さず、記録済みの結果をそのまま返すことです。
最初の要求で、サーバはまず冪等キーが処理済みかを保存領域(データベースなど)に問い合わせます。未処理なら決済を実行し、その結果を鍵とひも付けて記録し、クライアントに成功を返します。ここで応答が途中で消え、クライアントが同じ鍵で再送したとします。二度目の要求を受けたサーバは、鍵がすでに処理済みであることを見つけ、決済を実行し直すことなく、記録しておいた前回の結果をそのまま返します。こうして、クライアントから見れば「何度送っても一度分の決済しか起きず、同じ結果が返る」状態が実現します。再送が安全になるわけです。
実装上の要点は、「処理済みかの確認」と「処理と結果の記録」を、割り込みの余地なく行うことです。ここが緩いと、ほぼ同時に届いた二つの要求が両方とも「未処理」と判断し、二重に処理してしまいます。実務ではトランザクションや分散ロックを使ってこの一連の操作を保護します。また、記録した鍵をいつまで保持するか(保存期間)も設計事項で、短すぎると期限切れ後の再送で二重処理が起き、長すぎると保存領域を圧迫します。
メッセージ処理と分散設計での位置づけ
冪等性が最も真価を発揮するのが、メッセージキューを使った非同期処理です。多くのキューは、確実な配送のために「少なくとも一回(at-least-once)」の配送保証を採ります。これは「メッセージが消えるくらいなら、重複して届くほうがマシ」という割り切りで、裏を返せば「同じメッセージが複数回届くことがある」という前提です。このとき、受け手側の処理が冪等でありさえすれば、重複配送は何の問題も起こしません。重複を完璧に排除する仕組みをキューに作り込む代わりに、受け手を冪等にして重複を無害化する——これが分散システムにおける定石です。
同じ理由で、複数のサービスにまたがる処理を段階的に進めるSagaパターンでも、各ステップと、失敗時の巻き戻し処理(補償トランザクション)は冪等に設計するのが鉄則です。障害からの再試行でステップが再実行されても、状態が壊れないことが安全な回復の前提になるからです。
このように冪等性は、単なる小手先のテクニックではなく、「失敗と再試行が日常である分散システムを、いかにして信頼できるものにするか」という問いへの根本的な答えのひとつです。設計の早い段階で「この操作は再送されても大丈夫か」を問う習慣が、堅牢なシステムを作る分かれ目になります。