冪等性とは
ある操作を何回実行しても結果が同じであるという性質。
冪等性を保つ実装例
簡単な例
- クライアント側 : ボタンの複数クリック防止
- サーバ側 : 重複リクエストの解決
基本的にサーバ側で冪等性が保証されていると、より堅牢であるといえる。
障害復旧のケース
処理1で障害が発生したら、中間データを削除して再度処理1を行う(左図)。これを冪等性を保つようにするには、右図のように障害発生時に関わらず、中間データを削除する仕組みを処理1に含めればよい。
マイクロサービスやサーバーレスでの冪等性
サーバーレスとマイクロサービスはなぜ冪等性が必要?
- 一つの処理単位が小さく独立している
以下の図のようにモノリスアプリケーションの場合はDBトランザクションの原子性によって、整合性が保たれる。それに対して、マイクロサービスは複数のマイクロサービスによって、1つの業務トランザクションが成り立つ。ゆえに、エラーやリトライの処理が複雑になってしまう。
- リトライが自動で実行される
AWSのlambdaなどではリトライが自動で実行される。
ステートレスと冪等性
ステートフルとステートレス
- ステートフル : 記憶を使って判断する。1回目と2回目のレスポンスが異なる可能性がある。
- ステートレス : 記憶を保持しない。常に同じレスポンスを返す。
ステートフル | ステートレス | |
---|---|---|
スケーラビリティ | 低い | 高い |
データ保持 | サーバー側 | クライアント側 |
フェイルオーバー | 同期難しい | 問題少ない |
レスポンスの最適化 | ○ | × |
処理効率 | 高い | 低い |
ユーザ数やデータ量が増える場合があるなら、個々の処理のベストなレスポンスを求めるよりも、スケーラビリティの高いステートレスの方が向いている。
ステートレスの実現
ステートレスを実装するには外部DBなどにステートをデータとして保持すれば良い。
ただし、内部に保存する場合よりレスポンスに影響が出る、処理コードの複雑になる可能性がある。
早すぎる最適化は諸悪の根源
ドナルド・クヌースの論文 Computer programming as an art に 早すぎる最適化は諸悪の根源 という言葉がある。
内部でステートを保持したほうが効率的に思えるが、ある一時点での局所的な最適化を求めることは必ずしも良いことではない。ステートレスであることは、冪等性の確保、スケーラビリティや可用性の向上などの利点がある。
冪等性の実装方法
最も簡単に冪等性を担保する方法の一つがデータにユニークなIDを付与することである。
何を一意な値にするかは注意が必要。例えば、webショッピングで購入確定時に処理を呼ぶような場合、セッションID
は一意な値ではない。同じセッションで複数回の購入を行う可能性がある。購入処理のタイミングでセッションID+タイムスタンプ
で一意の値を生成する。
入力時チェック方式
データストアへの登録時にチェックする
- ユニークなID(冪等キー)を引数として受け取る。
- データストアへ格納時に、DBで一意性チェックを行う。
- 一意性チェックでエラーが出たら、複数回実行されたとみなし、例外処理でログに残し、正常終了。
- ログを定期的に監視する。本来エラーにすべきでないデータ投入処理がエラーになっていないかを確認する。
- 処理Xは、DBで一意性チェックエラー発生時、複数回の実行を検知し、ログにエラー情報を記録して正常終了。
出力時チェック方式
データストアからの取得時にチェックする
- 一意の値を引数として受け取る。
- データストアへの格納では、重複チェックをせず、全データを格納するようにする。
- データストアからの読み取り時に、重複を排除して取得する (SQLのDISTINCT句を使用するなど)
最後に
冪等性とは外側から見た性質である。上記で2つの実装方法を挙げたように、内部的な実装は関係ない。APIを使う側から、冪等かどうかを考えればよい。また、冪等性が保たれていても、異常終了した際には、整合性が損なわれる。ゆえに、ログ監視などはしっかり行うことが重要である。
参考文献
Tech Talk vol.2 Backend Engineer 〜マイクロサービスの冪等性〜 を開催しました
サーバーレスが気になる開発者に捧ぐ「べき等性」ことはじめ
実践 Microsoft Microservices