RaftにおけるreadIndex最適化

もっともシンプルなRaftの実装では、コマンドは常にリーダーに送られる。 リーダーはフォロアにレプリケーションし、過半数にコピーしたインデックスまではコミットしたことになる(このインデックスをcommitIndexと呼ぶ)。 リーダーはcommitIndexまではコマンドをステートマシン に適用(このインデックスをlastAppliedという)してもよく、 その結果をクライアントに返してもよい。

さて、この方法だと、 書き込みも読み込みもすべてリーダーで処理されるから、 性能的なスケールがない。

そこで、おれはこれがRaftの中でもっとも熱いところだと思っているのだが、 Raftの仕組みを応用して、 フォロアにも 線形可能性という強い一貫性のある読み込みを担当させることが出来る。 線形可能性とはざっくりいうと、分散システムではあるけども ノードが1つしかないように外界からは見えるということである。

これは、TiKVでもFollower Readとして実装されており、 おれが実装しているMulti-RaftライブラリSorockでも実装している。 これをreadIndex最適化と呼ぶ。これをざっくり説明する。

まず、クライアントの読み込みリクエスト(クエリ)がフォロアに届いた時、 その瞬間にリーダーでのcommitIndexがCだったとする。 この時、仮にフォロア内でlastApplied (<C) まで来た時にえいやと クエリを実行してしまったら、外界では、時間が巻き戻ったような現象が起こりうる。 なぜならば、実際には書き込みがCまで適用されてる可能性があるからだ。 これは、間違いである。

従って、lastAppliedは少なくともCまでは待つ必要があるということになり、 クエリを受け取ってから リーダーに対して「今のcommitIndexをください」とお願いして受け取った インデックスをreadIndexとして、このラインまでlastAppliedが進んだ時にクエリを実行すればいいことになる。(実際にはネットワークが分断してリーダーがstaleだった時のことを考える必要があるが、ざっくりとは正しい)

こうすることで、

  1. クエリはログの末端に並ばなくて済む。(あたかもCの後に並んだかのようになるため)
  2. クエリは並列に実行することが出来る。しかも結果はキャッシュ出来る。
  3. フォロアを増やせばクエリは無限にスケールする。

ということになる。

さて、このうち3番目だが、クエリを無限に増やすためにノードを無限に増やしたら、 今度はコミットが遅くなって、書き込みのレイテンシが上がる可能性が高い。

そこで、クラスタには存在するけどコンセンサスには参加しないという脇役のようなフォロアを定義する。これをLearnerとかNon-Voterとかいう。こいつらは立候補をしてリーダーにもなろうともしないし、選挙にも関係しない。従って、このLearnerを100台でも200台でも増やせば、クエリは本当に無限にスケールすることになる。Socorkではこれに関しても実装している。

プログラミングというと、 カーネル、コンパイラ、データベースあたりは 誰もが一度は作ってみたいものとして 今まで何度も学習のために作られてきた歴史があるが、 Raftに関してもそういう感じになってるという説がある。

comments powered by Disqus
Built with Hugo
テーマ StackJimmy によって設計されています。