Omidの仕組み
それでは、実際にOmidでどのようにトランザクションを実現しているかについて説明します。
アーキテクチャ
以下は、Omidのアーキテクチャを示した図になります。
最初の方にも書きましたが、TSOサーバはトランザクションに関する情報を管理し、データ更新の競合を監視するプロセスです。
Omidのクライアントは、TSOサーバ以外に、HBaseと直接やりとりを行います。
TSOサーバでは、BookKeeperというライブラリを使っています。BookKeeperは、Zookeeperを用いた信頼性のあるWAL(Write Ahead Log)ログサービスです。
TSOサーバでは、トランザクションに関する情報をメモリ上で管理しているため、障害などでサーバが落ちてしまった場合に、トランザクションの情報を消失してしまいます。そこで、TSOサーバはBookKeeper上にWALを書き込んでおき、いざというときにトランザクションの情報を復旧できます。
また、お気づきの方もいらっしゃると思いますが、OmidではTSOサーバがSPOF(Single Point of Failure)になります。現状のところ、TSOサーバの冗長化やロードバランシングをする手段は用意されていないようです(※1)。
MegaOmidと呼ばれる、TSOサーバをパーティショニングするブランチはあるようです。詳細はhttps://github.com/yahoo/omid/wiki/MegaOmidをご覧ください。
トランザクションの流れ
以下は、コミット成功が成功した場合のシーケンス図になります。
まず、クライアントからTSOサーバにトランザクションを開始することを宣言します。
その際に、TSOサーバはトランザクションの開始時間としてTimestampを生成し、それをクライアントに戻します。このTimestampは、TSOサーバ側で生成されるごとにインクリメントされていき、トランザクションを識別するものになります。
次に、クライアントはTSOサーバから受け取ったTimestampを用いて、データの参照・更新を行います。
データの更新時には、TSOサーバから受け取ったTimestampを指定して実行されます。データの追加・更新はHBaseに対してPutを行い、データの削除に関してはHBaseに対して空の値をPutすることで実現されます。
また、データの参照時にも、GetやScanにTSOサーバから受け取ったTimestampを指定して行われるとともに、コミットされていないデータが見えないようにフィルタリングをします。これにより、Snapshot Isolationが実現されます。
最後に、クライアント側からコミットリクエストを送信し、TSOサーバ側で変更されたRowに対して競合がないかを確認し、競合がない場合はコミット成功のレスポンスを送ります。
次に、コミットが失敗した場合のシーケンス図になります。
クライアントからトランザクション開始宣言をするところからコミットリクエストを送るまではまったく一緒です。
TSOサーバはコミットリクエストを受け取り、変更されたRowに対して競合がないかを確認し、競合があった場合は、コミット失敗のレスポンスを送ります。
クライアントは、コミット失敗のレスポンスを受け取ったら、更新処理を取り消すクリーンナップ処理を行います。具体的には、単純にHBaseからトランザクション中に行った更新に対してDeleteを行います。
クリーンナップ処理が完了したら、TSOサーバに対して完了通知を送ります。