Droongaの分散処理を支える技術：オーケストレーション

こんにちは。Droonga開発チームの結城（Piro）です。
Groonga Advent Calendar 19日目は、[18日目](http://qiita.com/piroor/items/20caac6e66ea228585e9)に引き続きDroongaの解説です。

Droongaを題材にした過去4回の記事（[6日目](http://qiita.com/piroor/items/38b389f8643a618fc86b)、[7日目](http://qiita.com/piroor/items/5747ec96fe86745d21e6)、[9日目](http://qiita.com/piroor/items/39b40f9fb1f52c061aae)、[13日目](http://qiita.com/piroor/items/47de24ab76ea7ed7c0b1)）では、分散処理の初歩の初歩を解説しました。

それに続くDroongaの基盤部分を紹介するシリーズとして、[16日目](http://qiita.com/piroor/items/2ca1e722c1bc48b17fce)はノード間の通信プロトコルについて紹介し、[18日目](http://qiita.com/piroor/items/20caac6e66ea228585e9)は、マスター無しでのクラスタ管理を可能にする仕組みについて紹介しました。

今回はその続きとして、クラスタ構成の動的な変更や、停止したノードに合わせたメッセージ転送ルートの動的な変更などを、Droongaではどのように行っているのかを紹介します。


## Droongaにおけるオーケストレーション

レプリケーションでは、同じデータベースを持つノードが複数ある状態になるため、一部のノードが停止しても全体としてはサービスを提供し続けられると述べました。
しかし、単に同じノードを複数用意しただけでは可用性は高まりません。停止したノードを除外してサービスを提供しないと、何回かに1回の割合で停止したノードにリクエストが行ってしまい、その時だけ結果がいつまで待っても返ってこない、という事が起こります。

![停止したノードがあるせいでサービスを提供できていない様子](https://qiita-image-store.s3.amazonaws.com/0/61387/8ec25182-5a52-ca5d-5996-f339f91bd3e0.png)

各ノードが正常動作しているかどうかを監視する事を、*死活監視*と言います。
また、死活監視も含めて、複数ノードを互いに協調して動作させることを*オーケストレーション*と言います。

死活監視があって初めて、可用性が高いシステムの運用が可能となります。
ノードの生死を正しく把握できれば、死んでいるノードを避けてメッセージを転送し合い、クラスタ全体としてはサービスを安定して提供し続けることができるというわけです。

![停止したノードを転送先から除外してサービス提供を継続している様子](https://qiita-image-store.s3.amazonaws.com/0/61387/4ea00fa0-6037-0d90-0ed8-e170286dbe45.png)


### Serfクラスタとの連携

とは言うものの、オーケストレーションを真面目に実現しようとすると、デッドロックの回避などのように注意しないといけない事がたくさんあって、なかなか大変です。

幸い、オーケストレーションの基本的な要素を提供する[Serf](https://www.serfdom.io/)というツールがありましたので、Droongaではこれをそのまま採用しています。

Serfのエージェントを起動しておくと、同様にSerfが動作しているノードとの間でSerfクラスタを形成します。

![DroongaクラスタとSerfクラスタの連携の様子](https://qiita-image-store.s3.amazonaws.com/0/61387/46818494-b7a0-ef2f-d426-9e55aded9956.png)

Serfが起動している間は、どのノードが稼働していてどのノードが停止しているかといった情報がクラスタ全体で共有されますし、クラスタ全体に一斉にメッセージを送ることもできます。
また、Serfクラスタの情報とカタログの情報を照合することで、DroongaノードはDroongaクラスタ内で利用可能なノードの一覧を把握します。
メッセージの転送先を決定する際はこの情報を併用して、停止中のノードへのメッセージ転送が発生しないようにしています。

なお、Serfはコマンドライン引数や環境変数といった形で様々な指示を受け取りますが、Droonga内部の仕組みから利用しやすいように、独自に開発したRubyによるwrapperを介してSerfを使っています。
（プロトコルアダプターである`droonga-http-server`でも同様に、Node.js用のwrapperを開発してSerfを利用しています。）


### Droongaクラスタの構成変更

Droongaクラスタでは、すべてのノードがカタログによってクラスタ全体の構成を把握していると述べました。
カタログに記載があればメッセージが転送されますし、記載がなければ転送されません。
Droongaにおけるノードの追加や切り離しといったクラスタ管理は、すべて、このカタログを更新することによって行われます。

しかし、Droongaクラスタ内でのメッセージ転送の仕組み上で、転送先を決定するためのクラスタ構成自体を変える指示を送り合うのは、高速道路を走っている最中に車のハンドルを交換するような不安があります。

![クラスタ構成の変更中にクラスタが消失してしまった様子](https://qiita-image-store.s3.amazonaws.com/0/61387/e316b547-2c12-cf31-2553-a84a31056a8e.png)

なので、Droongaではクラスタ変更の指示はSerfクラスタを経由して行っています。
管理コマンドではSerfの仕組みの上でクラスタ変更指示のメッセージを一斉通知し、各Droongaノードはそれを受信して、自らの持つカタログを一斉更新します。
以下は、クラスタに新たなレプリカを追加する場合の例です。

![Serfを使ったクラスタ変更の処理の流れの概略図（droonga-engine-joinの場合）](https://qiita-image-store.s3.amazonaws.com/0/61387/561166c0-2023-050d-3633-285688797acc.png)

Serfの仕組みの上で送れるメッセージのサイズには厳しい上限がありますので、必要な情報をすべてSerfのメッセージで送るということはできません。
なので、基本的には最小限の指示だけをSerf上でやりとりし、Droongaノード間でのデータのコピーなどの重たい処理が必要になった時にはDroongaノード間の通常の通信手段で別途データをやりとりするようにしています。

カタログが更新されると、それ以後は新しいカタログの記載内容に基づいてメッセージの転送先が決定されるようになります。
「クラスタにノードが加われば自動的にそのノードへメッセージが転送されるようになり、ノードが離脱すればそのノードへはメッセージが転送されなくなる」という動作は、このようにして実現されています。



## まとめ

では、ここまでの話をまとめます。

 * Droongaではノードの状態に合わせてメッセージの転送ルートを動的に変更しています。
 * DroongaではSerfを使って、ノードの死活監視やクラスタ変更のための指示のやりとりを行っています。


## Droongaが目指すところ

ここまでで述べた通り、Droongaは分散処理の基盤としての仕組みを持っており、Groonga互換機能はその上で動作するプラグインとして実装されています。
プラグインを新たに実装しさえすれば、検索やデータベースと関係の無い物、例えば「リクエストキューの生成パターンを指示するリクエストを送ると、各ノードがリクエストキューを自分で自動生成して、各ノードが一斉に特定のサービスへリクエストを送り始める」という風な、大規模なストレステストやベンチマークのための仕組みもDroongaの上で構築する事ができます。

また、Droonga自体がRubyで開発されているということもあり、各Droongaノード上で分散して行わせたい処理はRubyで記述できます。
*「LL（LightWeight Language：軽量言語）で簡単に使える、汎用分散処理システムのフレームワーク」*
これこそが、Droongaが最終的に目指している所だと言えるでしょう。

なお、Droongaがパーティショニングに関して現在実装している「クエリを分散し、結果を集約する」という分散処理アルゴリズムは、一般的には[MapReduce](http://www.atmarkit.co.jp/ait/articles/0807/08/news119.html)と言います。
元々はGoogleで考案された方法で、Javaベースの大規模分散データ処理のフレームワーク「Hadoop」もこの理論に基づいて開発されています。
MapReduceの理論は最初に発表されてからもう10年ほどが経っており、大規模データの分散処理の分野での最先端の研究や実装は、そこからさらに進歩しています。
Droongaプロジェクトでも、いずれはそれらの成果を取り込んでいきたいと思っています。


## おわりに

以上、Groonga Advent Calendarの枠をお借りし、合計6回に渡ってDroongaにおける分散処理を解説してみました。
いかがだったでしょうか？
「分散処理は分からないから……」と敬遠されていたような方に、この解説をご覧になって「なるほど、分散とはこういう事なのか！」と思っていただければ幸いです。
記事を読んで興味が湧いたという方は、[Droongaのリポジトリ](https://github.com/droonga/droonga-engine)や[Droongaのプロジェクトサイト](http://droonga.org/)も覗いてみて下さい。

Droongaプロジェクトはまだまだ発展途上で、実装が追いついていない部分もたくさんあります。
現在は、既にGroongaを利用されている方が安心してDroongaに移行できるようにするべく、「レプリケーションできるGroonga互換の全文検索システム」を当面の目標として、完全無停止でのクラスタ構成変更の実現や、未実装のコマンドの整備によるGroongaとの互換性の向上などに取り組んでいます。
自分が使っているGroongaのこの機能がまだDroongaでは利用できないので移行できない、という部分を見付けた方は、[Issue Tracker](https://github.com/droonga/droonga-engine/issues)（日本語で登録してOKです）でご報告いただいたり、プルリクエストをお送りいただけたりすると、大変助かります。

それでは、残りの日程も1週間を切りましたが、引き続きGroonga Advent Calendarをお楽しみ下さい！