@twitterこの広告は、90日以上更新していないブログに表示しています。あらゆる操作を冪等にする方法いつも心に冪等性。リトライと冪等性のデザインパターンの完結編です。最後に冪等性を実装する汎用的な実装手法についてまとめていきます。パターン6:操作ログとリクエストIDでUPDATEを冪等にする同じIDで識別される値がUPDATEされる場合、つまりmutableである値の管理は、一般に冪等に行うのが難しい。ユーザーAが最後に購入したアイテムをアイテム1に変更する(UPDATE)をリトライ→成功。内容が巻き戻る!このような処理を冪等にする方法はいくつもあるが、操作ログとリクエストIDを使う手法は非常に汎用性が高い。覚えておけば色々な場面に適用できる。値を取り出すときは、操作に付けられた時刻に基づいてログをソートし、最新の物を使えばいい。操作ログに付けるIDと時刻は、操作自体が行われた時に生成した物を使う必要がある。操作ログを挿入する瞬間の時間を使うと、リトライ時に書き込んだ古い内容が新しい操作だと誤判定されてしまうので要注意。操作ログを扱う場合、時間が経つにつれてレコード数が増えていき、ストレージ容量を圧迫してくる。このため、古い操作ログを削除したり別ストレージに移す処理を追加する必要が出てくる。パターン7:最終奥義「トランザクション」あらゆる操作は冪等であることが望ましい。冪等ならば自動的にリトライできるので信頼性を高めやすいし、リトライしても副作用が無いと分かっていれば障害発生時に対処もしやすい。冪等性を組み込もうと考えると、どうしてもコードが複雑(難解)になってしまう。いう場合にはトランザクションを活用することができる。トランザクションは一連の操作を「すべて成功」(全体を確定する)または「すべて失敗」(全体を巻き戻す)のどちらかになるように保証してくれる。一連の操作の中に一つでもリトライを検出できる操作が入っていれば、簡単に全体の冪等性をできる。「アイテムが購入されたら、所定の更新処理を色々と行う」というケースを考えてみる。「所定の更新処理」は複雑で、どうにも冪等な実装が煩雑だとしよう。一方で「アイテムを購入する」の方は、操作にIDを付けて冪等にしたとする。XXXが既に完了しているので、全体としては冪等性になる。パターン6の例も、この方法を使えばよりシンプルに冪等にすることができる。APIや外部のストレージへの操作を伴う場合は、それら個々の操作を冪等にする必要がある。commitなど、複数のDBにまたがった操作を単一のトランザクションで実行する方法もあるが、分散トランザクションの技術についてはここでは触れないことにする。冒頭でも触れたように、リトライの粒度を大きくすれば信頼性は落ちるという点には注意が必要。上記のケースでは、「所定の更新操作C」が3%の確率で失敗する場合、「アイテム1の購入」も3%の確率で道連れにされ失敗する。信頼性が要求される操作と失敗しやすい操作が混じっている場合は、同一のトランザクションで実行すると悪影響が大きい。失敗しやすい操作は非同期で実行することで、トランザクションの粒度は小さく保った方がいいだろう。おわりに3回に渡る記事(初回・2回目)となりましたが、いかがだったでしょうか。Dataの同僚とリトライに関する議論をするときに、基礎知識として共有する内容を書こうと思ったのがきっかけでしたが、あらゆる場面で分散システムの知識が要求される昨今のプログラミング環境においては、リトライと冪等性は必修科目なのではないかと思われます。FluentdやEmbulkなどのデータを扱うシステムはリトライを前提にした設計をする必要があり、Digdagもエラーを重複なくリトライすることが重要なシステムなので、大いに役に立っています。APIとDB操作が冪等になっており、エラーが起きても自動でリトライするようになっています。いま手がけているリアルタイムデータ処理システムでも、PlazmaDBの実装で使用しているパターンの多くを再利用しています。@frsyukiまで。*1:この手抜きは多くの場合に妥当だと思う。消費ストレージ容量をモニタリングする監視やアラートは追加しておいた方がいい。Ru...2015-02-16並列データ転送ツール『Embulk』リリース!こんにちは。