Droonga 1.1.0での「hot add」の仕組みの説明

2015年4月29日付けでリリースしたDroonga 1.1.0 http://droonga.org/news/2015/04/29/release.ja.html ではreplicaノードのhot addが可能になったという事が最大のトピックでしたが、それがどのように実現されているのかについての説明です。
0
Piro🎉"シス管系女子"シリーズ累計5万部突破!!🎉 @piro_or

連休中のこんな時間に書いてもしょうがないんだけど、吐き出しとかないと自分が先に進めないのでDroonga droonga.org/ja/ の事について書いときます。実装面の話です。

2015-05-03 03:50:56
リンク Droonga Droonga Droonga is a distributed full text search engine.

Droonga 1.1.0での各変更点がhot addの実現にどのように貢献しているか

Piro🎉"シス管系女子"シリーズ累計5万部突破!!🎉 @piro_or

今回のリリース droonga.org/news/2015/04/2… では上から下までアホみたいにたくさんの変更が入ってるんですが、これらは基本的には全て、「hot add」つまりデータの書き込み操作を止めずにノードを追加できるようにするための変更なのでした。

2015-05-03 03:53:18
リンク Droonga Droonga 1.1.0をリリースしました! | Droonga Droonga is a distributed full text search engine.
Piro🎉"シス管系女子"シリーズ累計5万部突破!!🎉 @piro_or

昨年末時点では「書き込みを止めないノード追加の実現まであとちょっと」と言ってたんですが、実際着手してみるといろいろ直さないといけない部分が出てきて、ほぼ1ヶ月ちょいも作業にかかってしまいました。(年始からしばらくは別件に張り付いてたのでそもそも作業が進行してなかった)

2015-05-03 03:56:42
Piro🎉"シス管系女子"シリーズ累計5万部突破!!🎉 @piro_or

まず1つ目、ノード間のメッセージ配送に関わる経路の整理。前のバージョンではパケットの送出はForwarderというクラスの単一のインスタンスが一手に引き受けてたんですが、これを、送出先のノード1つにつき1インスタンス持つようにしました。

2015-05-03 04:01:52
Piro🎉"シス管系女子"シリーズ累計5万部突破!!🎉 @piro_or

何故かというと、送出先のノードそれぞれに対して個別に「書き込みバッファ」を用意したかったからです。元々バッファの仕組みは入ってたんですが、それはインフラレベルの通信途絶などに際しての自動再送用で、生きている他のノードに対して任意に「今は送らないで」みたいな制御はできませんでした。

2015-05-03 04:07:44
Piro🎉"シス管系女子"シリーズ累計5万部突破!!🎉 @piro_or

それで、元からあるバッファ「accidental buffer」の手前にもう一段、「intentional buffer」を設けました。

2015-05-03 04:11:37
Piro🎉"シス管系女子"シリーズ累計5万部突破!!🎉 @piro_or

これにより、「このノードはまだデータの同期が終わってないから新規レコード追加のリクエストは送らず溜めておく」「同期が終わったから、その間止めて溜め込んでいた新規レコード追加のリクエストを放出する」というような柔軟な制御が可能になりました。

2015-05-03 04:12:44
Piro🎉"シス管系女子"シリーズ累計5万部突破!!🎉 @piro_or

次に、roleという概念の導入。各ノードはservice-provider, absorb-source, absorb-destinationのいずれかのroleを持つようにしました。

2015-05-03 04:14:56
Piro🎉"シス管系女子"シリーズ累計5万部突破!!🎉 @piro_or

s-pはいわゆる普通のノード。a-dはクラスタに追加しようとしている新しいノード、a-sはそのノードに対するデータのコピー元となるノードです。この内、s-pになっているノードだけが、これまでのノードのように外向けのサービスを提供します。

2015-05-03 04:18:30
Piro🎉"シス管系女子"シリーズ累計5万部突破!!🎉 @piro_or

s-pが新規レコード追加のリクエストを受け付けると、他のs-pに対しては即座にリクエストを転送しますが、それ以外のroleのノードに対しては、書き込みバッファに書き出してそれで終わりとなります。

2015-05-03 04:21:10
Piro🎉"シス管系女子"シリーズ累計5万部突破!!🎉 @piro_or

なぜそんな事をするかというと、新規に追加するノードにも、そのノードに対するデータのコピー元ノードにも、同期が完了するまでの間は変更を加えられると困るからです。

2015-05-03 04:24:27
Piro🎉"シス管系女子"シリーズ累計5万部突破!!🎉 @piro_or

a-sからa-dへのデータの同期が完了したら、これらのノードのroleはservice-providerに戻ります。そうするとやっと、その間止めていたレコード追加のリクエストがバッファから吐き出されるようになります。

2015-05-03 04:29:02
Piro🎉"シス管系女子"シリーズ累計5万部突破!!🎉 @piro_or

バッファが空になって、それらのリクエストが全て処理されたら、a-dとa-sはs-pと同等のデータを持つ状態になる。というわけ。

2015-05-03 04:32:01
Piro🎉"シス管系女子"シリーズ累計5万部突破!!🎉 @piro_or

寝落ちしてた……夜中の続き、Droonga 1.1.0 droonga.org/news/2015/04/2… の実装面の変化の話。データの同期が完了した後、溜まってた書き込みバッファをそのまま愚直に処理すると、レコードに重複が生じてしまいます。それを防ぐための仕組みを整備しました。

2015-05-03 09:20:47
Piro🎉"シス管系女子"シリーズ累計5万部突破!!🎉 @piro_or

何故レコードが重複するかというと、タイミングの問題で、新しく追加されるノードには「データの同期でa-sから取り込んだレコード」と「そのレコードの元になった、レコード追加のリクエスト」の両方が届いてしまうから。

2015-05-03 09:24:33
Piro🎉"シス管系女子"シリーズ累計5万部突破!!🎉 @piro_or

ノードの追加は、「1、新規ノード追加」「2、a-sにあたるノードの戦線離脱」「3、a-sから新規ノードへのデータの同期」「4、a-sの戦線復帰」「5、新規ノードの戦線投入」の順で行われる。新規ノード向けの書き込みバッファは、1〜4の間使われる。

2015-05-03 09:30:44
Piro🎉"シス管系女子"シリーズ累計5万部突破!!🎉 @piro_or

すると、1,2の間にバッファに溜まったリクエストの中には、既にa-sでレコードとして追加されデータの同期を通じて取り込み済み、というものが出てくる。主キーのあるテーブルへのレコードの追加なら、そういう重複したリクエストは単に「既存レコードの更新」扱いになるから問題ない。

2015-05-03 09:34:55
Piro🎉"シス管系女子"シリーズ累計5万部突破!!🎉 @piro_or

しかしGroongaには主キーを持たないテーブルというのが存在し得る。そういうテーブルでは、レコード追加のリクエストが来たら来た分だけレコードが増える。その結果、このままでは新規に追加したノードだけレコード数が多くなるということになってしまう。

2015-05-03 09:37:15
Piro🎉"シス管系女子"シリーズ累計5万部突破!!🎉 @piro_or

なので、a-sは自分が処理した最後のリクエストはいつ時点のものだったかというタイムスタンプをきちんと保持するようにして、既存の各ノードは、新規ノードに対してはその日時以降のリクエストのみを書き込みバッファから送出する(それより前のリクエストは捨てる)ようにしました。

2015-05-03 09:41:00
Piro🎉"シス管系女子"シリーズ累計5万部突破!!🎉 @piro_or

このために、データベースへの書き込みを伴うメッセージにはマイクロ秒単位のdateの指定が強く推奨されるようになりました。droonga-clientもdroonga-http-serverも、メッセージには基本的にいつもマイクロ秒単位のdateを付けるようになりました。

2015-05-03 09:44:03
Piro🎉"シス管系女子"シリーズ累計5万部突破!!🎉 @piro_or

droonga-http-server(express-droonga)はNode.jsで作られてますが、そのままだとミリ秒までの精度でしかtimestampを設定できないため、マイクロ秒単位の時刻を扱えるnpmのライブラリを使うようにしてます。

2015-05-03 09:46:30
Piro🎉"シス管系女子"シリーズ累計5万部突破!!🎉 @piro_or

最後に、完全なGraceful Restartの実現。これまではGraceful Restartの処理が結構いい加減で、再起動の直前・最中に流入したメッセージについて、どこまでは処理できてどこからは処理できてないということを厳密に判断できない状態でした。

2015-05-03 09:49:50