Droongaで理解する、分散処理の基本のキ：リスクの分散

こんにちは。Droonga開発チームの結城（Piro）です。
Groonga Advent Calendar 7日目は、[昨日](http://qiita.com/piroor/items/38b389f8643a618fc86b)に引き続き、Droongaを肴にした分散処理の初歩の初歩の解説です。

[前回の記事](http://qiita.com/piroor/items/38b389f8643a618fc86b)では、分散には以下の3つのメリットがあると述べました。

 * *負荷の分散*により、1台のマシンの性能の限界を超えた処理が可能になる。
 * *リスクの分散*により、可用性が高まる。
 * *データの分散*により、今までは扱いきれなかった量のデータを扱えるようになる。

このうちの*負荷の分散*については、前回の記事で解説しました。
この記事では、「*リスクの分散*により、可用性が高まる」という点について解説します。

## リスクの分散

前日の記事では「負荷の分散によって処理能力の限界を超えられる」という事を述べました。
ということは、逆に言えば、「処理能力に余裕がある状況ならば分散する必要は無い」とも言えます。

しかし、分散にはもう1つのメリットがあります。
それが、*可用性が高まる*という点です。

### サーバが1台しか無いのはハイリスク

仮に1台のGroongaサーバだけを運用していたとして、そのサーバが突然利用できなくなってしまったら、非常に困ります。

 * ハードウェアが故障してしまったら？
 * メモリ不足でスラッシングが発生してしまったら？
 * ハードディスクの空き容量がなくなってしまったら？

データを定期的にバックアップすれば問題ないと思うかもしれませんが、その場合でも、バックアップからデータを復旧するまではサービスは停止したままです。

![単一のGroongaサーバでサービスを提供している様子](https://qiita-image-store.s3.amazonaws.com/0/61387/c2b2591a-0b74-2fca-1bf5-afd52c3407e9.png)


このように、何かトラブルがあった時に復旧が完了するまでサービスが全く利用できなくなってしまう事を、「可用性が低い」と言います。
（※可用性とは、「そのシステムがどれだけ安定して継続的に稼働できるか」を指す言葉で、信頼性や耐障害性といった概念とも関係が深いです。とりあえずは、それらとだいたい同じような意味の言葉と思ってもらって差し支えないでしょう。）

サーバが1台しかない状態では、どうしても可用性が低くなってしまいます。
商用サービスのバックエンドのように、安定したサービス提供が求められる場面では、この状態で運用していると安心できませんよね。


### 冗長化・多重化

先程、Droongaでは各ノードはそれぞれ同じ内容のデータベースを持っているという事を述べました。
このような状態を「冗長化されている」あるいは「多重化されている」と言います。
ノードが冗長化されているということは、仮にクラスタ内のノードがいくつか停止したとしても、抜けた穴を他のノードが肩代わりできて、システム全体としてはリクエストを処理し続けられる、という事を意味します。

![Droongaクラスタの一部のノードが停止していてもサービスを提供できている様子](https://qiita-image-store.s3.amazonaws.com/0/61387/87a013e5-e905-c109-e4e8-0977afd1dd0c.png)


ただし、同等のノードが複数あるだけでは可用性は高まりません。
単にランダムにリクエストを転送しているだけだと、たまたま転送先に不調なノードが当たってしまったら、そのリクエストに対するレスポンスはいつまで経っても返せないことになります。

突然のトラブルに際しても慌てないで済むためには、ノードの生死の状態を監視し、停止したノードはリクエストの転送対象から除外する、ということを自動的・暗黙的に行う必要があります。
この記事では説明しません（後の記事で扱います）が、Droongaにはまさにそのための仕組みが含まれています。

ちなみに、停止したノードを除外して動作しているクラスタは、全体の処理能力が元の状態よりも落ちてしまいます。
前項で「ノード数が増えると限界性能が引き上げられる」と説明しましたが、これは裏を返すと、「ノード数が減ると限界性能が落ちる」ということになるわけですね。
このように、サービスが停止しているわけではないけれども応答時間が増えるなどの悪影響が出ているという状態を、「サービスレベルが落ちている」と表現することもあります。


### リアルタイムで効率よく冗長化する

ノードを冗長化するには、各ノードに同じ内容のデータベースを持たせなくてはなりません。
「データの投入時に、各ノードに同じデータを投入する」「どれか1台のノードにデータを投入して、できあがったデータベースを他のノードにコピーする」などいくつかの方法が考えられますが、Droongaでは、「どれか1つのノードがデータの追加や変更を伴うリクエスト（更新リクエスト）を受け取ったら、それを自動的に他のノードにも転送する」という方法を取っています。

以下は、`table_create`や`load`などの更新リクエストをDroongaクラスタが受け取った際の処理の流れの概略図です。

![更新リクエストが他のノードに転送される様子の概略図](https://qiita-image-store.s3.amazonaws.com/0/61387/5ab71983-9107-ebc1-2c2b-b7ca05753fb1.png)

更新リクエストを受け取ったノードは、そのHTTPリクエストをDroongaクラスタ内部での通信用メッセージに変換してから、fluentdプロトコルに乗せて、クラスタ内の他のノードにメッセージを分散転送します。
どのノードであっても、外から来た更新リクエストは必ず他のノードにも転送します。
これにより、各ノードがそれぞれお互いに同等の情報を持っているという状態が維持されます。

複数のノードが同時にクラスタ外から更新リクエストを受け取った場合も同様です。

![複数の更新リクエストが並行して他のノードに転送される様子の概略図](https://qiita-image-store.s3.amazonaws.com/0/61387/16ba7132-bece-b7af-1918-d0cbb75d28e6.png)


この場合、更新リクエストを受け取ったそれぞれのノードが別々にメッセージを分散転送します。
これらの処理は完全に並行して行われるため、ノードがどちらの更新リクエストを先に受け取るかは一定しません。
ですので、Droongaで安定した検索結果を得たい場合には、ソートされた検索結果を返すように指示する必要があります。

また、更新リクエストと検索リクエストが並行して流入してくる場合には、あるノードでは処理済みの更新リクエストが、他のノードでは未処理であるという風に、一時的に各ノード間で情報のばらつきが生じることがあります。
このような状況においては、同じ検索リクエストであっても、リロードの度に結果が変わるということもあります。

そんな不安定なサービスで大丈夫なのか？と心配に思う方もいるかもしれませんが、これはGroongaの設計方針には合致しています。
[Groonga Advent Calendar 1日目の記事](http://qiita.com/groonga/items/98b526802a6e9a0d44ae)での紹介にある通り、Groongaは情報の鮮度を優先した、投入されたばかりのデータをいち早く検索結果に反映する設計の検索エンジンです。
ですので、データの投入中に検索を実行すると、タイミングによっては結果が一定しないということも普通に起こり得ます。
「すべてのノードの情報が完全に同期されたことを確認できるまで検索結果に新しいデータを反映しない」というのは、この方針にはそぐわないでしょう。
（どうしてもそのような要件がある場合には、Droongaではプラグイン機構を使って何らかの別の方法を用意する必要があります。）

なお、スキーマ変更を伴う更新リクエストの扱いについてだけは注意が必要です。
Droongaでは複数のノードがクラスタ外から更新リクエストを受け付けた場合、それらがどの順番で各ノードに到達するかを保証できません。
そのため、例えば`table_create`や`column_create`などのリクエストを複数のノードに送ると、タイミング次第では、テーブルができる前にカラムを作ろうとしてエラーになってしまう、という事が起こります。
ですのでDroongaでは、スキーマ変更を伴うリクエストだけは必ず1つのノードに順番に投入しなくてはいけません。
（同じノードが順番通りに受け取ったリクエストは、その通りの順番で他のノードにも転送されます。）
この点にはくれぐれもご注意下さい。



## それってただのマルチサーバ構成じゃないの？

……と、ここまでの説明を見て「何を当たり前の事を言っているんだ？」と思った方もいることでしょう。

このような負荷とリスクの分散のさせ方は「マルチサーバ構成」「複数台構成」などと言い、Webアプリケーションの運用などでもよく見られる形態です。
Groongaを実際にお使いになられている方の中にも、こういう構成で運用されている方はいらっしゃいますし、筆者もそのようなサービスの運用に関わったことがあります。

MySQLなどのデータベース製品では、このような負荷分散・冗長化・多重化を行う構成のことを*レプリケーション*と呼んでいます。Droongaでも同じ用語を採用しており、レプリケーションを構成するノードは*レプリカ*と呼んでいます。
現在のDroongaを指しての「レプリケーション有りのGroonga互換の全文検索システム」という説明には、このような意味が含まれています。

### 分散を前提に置いているということ

Droongaのような分散型のシステムの最大の特徴は、*このような運用形態を最初から前提に置いているために、分散構成を運用しやすくなるような様々な便利機能が最初から備わっている*という事です。

というのも、Groongaは元々そのようなマルチサーバ構成での運用を想定していません。
（※「そのような使い方をしてはならない」という意味ではなく、文字通りの、「そのような使い方のための便利機能は含まれていない」という意味です。）
ですので、マルチサーバ構成で安定してGroongaを運用するためには、

 * どうやってリクエストを分散させるか？
 * どうやってノードの生死の状態を監視するか？
 * ノード群へのノードの追加や、ノードの切り離しなど、クラスタの管理をどうやって行うか？

といった点で工夫が必要になります。

実際に、これらの課題を解決してマルチサーバ構成でGroongaを運用するという事については、既に以下のようなノウハウがあります。

 * Mroongaを使い、MySQL/MariaDBのレプリケーションの仕組みに載っかる。
 * LVSとkeepalivedなどを使い、リクエストを分散させる。
 * fluentdにfluent-plugin-groongaを組み合わせて使う。
 * [Madhand](https://github.com/yappo/ruby-madhand)を使う。

他方、Droongaは最初から分散型として設計されており、前述したような課題の解決策があらかじめシステムに含まれています（もちろん、現時点では未解決の課題についても積極的に取り組んでいく方針です）。
そのためDroongaでは、Droongaノードとしてのセットアップやクラスタ管理を行うだけで、「安定して使える、レプリケーションありのGroonga互換の全文検索システム」として利用できるようになっています。


### Droonga＝Ruby製Groonga？

そういえば、先日のイベントでの発表時に寄せられたコメントで、DroongaはRubyでGroongaを再実装しているようだという指摘がありました。
これは、ある意味では正解です。

「Groonga」と呼ばれる物は、実は2つあります。
1つは、参照ロックフリーなどの特徴を持つ全文検索エンジンライブラリとしてのGroongaで、「libgroonga」とも呼ばれます。
もう1つは、コマンドラインから起動できる全文検索エンジンアプリケーションとしてのGroongaで、「groongaコマンド」とも呼ばれます。

libgroongaはライブラリなので、それ単体では使えませんし、アプリケーションに組み込むにもそれなりの知識が必要です。
コマンドラインインターフェースやHTTPサーバなどのユーザーインターフェースを提供して、libgroongaが持つ全文検索機能を一般のユーザにも分かりやすい形で利用できるようにしたアプリケーションが、groongaコマンドということになります。

先程の「Groongaは元々そのようなマルチサーバ構成での運用を想定していません」という説明は、どちらかといえば「groongaコマンド」の説明です。
これになぞらえれば、現在の「Groonga互換の全文検索システム」としてのDroongaは、「groongaコマンドの同等品を、分散処理の機能も含めて、Ruby（等）で再実装している」と表現することもできるでしょう。

![libgroongaとgroongaコマンドの関係を表した図](https://qiita-image-store.s3.amazonaws.com/0/61387/8f683f69-ef0f-e2c1-e77d-62ceb8621cd3.png)

Droongaと他のGroongaレプリケーションの試みとの違いは、ここにあります。
他のGroongaレプリケーションの試みでは、groongaコマンドを基盤として用い、その上にレプリケーションの仕組みを導入している場合がほとんどです。

![groongaコマンドを介してlibgroongaを使うために、全力を引き出せていない](https://qiita-image-store.s3.amazonaws.com/0/61387/db2d7947-0937-f21e-9578-316363f1653f.png)

それに対してDroongaでは、libgroongaをRroonga（libgroongaのRubyバインディングで、libgroongaの低レイヤのAPIへのアクセス手段を提供する）経由で利用するという、*より低いレイヤに踏み込んだ設計*となっています。
そのため、libgroongaが本来持っている、groongaコマンド経由では利用できなかった様々な機能を活用できますし、処理の分散のさせ方を細かく最適化できる余地があります。

![Droongaが直接libgroongaを使う事で、より高度な事ができる余地がある](https://qiita-image-store.s3.amazonaws.com/0/61387/517a6d8e-8cb7-27ce-5dbc-ad19c6f4a872.png)

ただ、イベントでの発表時にも強調したのですが、これはあくまでDroongaの数ある側面のうちの1つに過ぎないという点には注意していただきたい所です。

ここまでの解説で、各ノードがメッセージを受け取って検索処理やデータ更新処理を行うということを述べました。
Droongaではこのような、各ノードでメッセージを処理するモジュールの事を、Handlerと呼んでいます。

Handlerはプラグインとして定義されており、ここまでに述べた処理も`search`、`crud`、`groonga`といった組み込みのプラグインで実装されています。
また、プラグインを新たに開発すれば、「新たに投入されたデータについて条件にマッチする物があれば、Push型メッセージでクライアントに通知する」「各ノードで全文検索以外の集計処理を行い、結果を集約して返す」といった、様々な処理を各ノードで実行することもできます。
むしろ、「分散データ処理のための汎用の基盤」というのがDroongaの本質で、Groonga互換の全文検索機能はその上に構築された一応用形態に過ぎないのです。


## まとめ

ここまでの話をまとめましょう。

 * Droongaはリスクの分散により、単一のGroongaサーバと比べて可用性が高くなります。
 * Groongaをレプリケーションする試みはいくつかあります。
   Droongaは最初から（レプリケーションを含む）分散構成を前提に開発されています。
 * Droongaでは、各ノードで実行する処理をHandlerプラグインという形で任意に実装・追加できます。

## 次回予告

ここまで、レプリケーションという分散の仕方によって、安定して大量のリクエストを処理し続けられるシステムを実現できるという事を解説しました。
しかし世の中にはレプリケーションだけでは解決できない問題があります。
それは、扱うデータ量の増大です。

[Groonga Advent Calendar 9日の記事](http://qiita.com/piroor/items/39b40f9fb1f52c061aae)では、分散処理の利点の3つ目「*データの分散*により、今までは扱いきれなかった量のデータを扱えるようになる」という事について解説します。
乞う御期待！