Droongaチュートリアル: 既存クラスタへのreplicaの追加

チュートリアルのゴール

既存のDroongaクラスタについて、新しいreplicaを追加し、既存のreplicaを削除し、および、既存のreplicaを新しいreplicaで置き換えるための手順を学ぶこと。

前提条件

「replica」とは?

Droongaのノードの集合には、「replica」と「slice」という2つの軸があります。

「replica」のノード群は、完全に同一のデータを持っており、検索などのリクエストを各ノードが並行して処理する事ができます。 新しいreplicaを追加する事によって、増加するリクエストに対して処理能力を増強することができます。

他方、「slice」のノード群はそれぞれ異なるデータを持ちます(例えば、あるノードは2013年のデータ、別のノードは2014年のデータ、という具合です)。 新しいsliceを追加する事によって、増大するデータ量に対してクラスタとしての容量を拡大することができます。

現在の所、Groonga互換のシステムとして設定されたDroongaクラスタについては、replicaを追加することはできますが、sliceを追加することはできません。 この点については将来のバージョンで改善する予定です。

ともかく、このチュートリアルでは既存のDroongaクラスタに新しいreplicaを追加する手順を解説します。 早速始めましょう。

既存のクラスタに新しいreplicaノードを追加する

このケースでは、検索のように読み取りのみを行うリクエストに対しては、クラスタの動作を止める必要はありません。 サービスを停止することなく、その裏側でシームレスに新しいreplicaを追加することができます。

その一方で、クラスタへの新しいデータの流入は、新しいノードが動作を始めるまでの間停止しておく必要があります。 (将来的には、新しいノードを完全に無停止で追加できるようにする予定ですが、今のところはそれはできません。)

ここでは、192.168.0.10192.168.0.11 の2つのreplicaノードからなるDroongaクラスタがあり、新しいreplicaノードとして 192.168.0.12 を追加すると仮定します。

新しいノードをセットアップする

まず、新しいコンピュータをセットアップし、必要なソフトウェアのインストールと設定を済ませます。

(on 192.168.0.12)
# apt-get update
# apt-get -y upgrade
# apt-get install -y ruby ruby-dev build-essential nodejs nodejs-legacy npm
# gem install droonga-engine
# npm install -g droonga-http-server
# mkdir ~/droonga

ここで、以前にクラスタを構築する時に catalog.json を生成するために実行したコマンド列を思い出して下さい:

(on 192.168.0.10 or 192.168.0.11)
# droonga-engine-catalog-generate --hosts=192.168.0.10,192.168.0.11 \
                                  --output=~/droonga/catalog.json

新しいノード用には、--host オプションの値以外はすべて同じ指定で、単一のノードだけを含む catalog.json を生成します:

(on 192.168.0.12)
# droonga-engine-catalog-generate --hosts=192.168.0.12 \
                                  --output=~/droonga/catalog.json

では、サーバを起動しましょう。

(on 192.168.0.12)
# cd ~/droonga
# host=192.168.0.12
# droonga-engine --host=$host \
                 --log-file=$PWD/droonga-engine.log \
                 --daemon \
                 --pid-file=$PWD/droonga-engine.pid
# env NODE_ENV=production \
    droonga-http-server --port=10041 \
                        --receive-host-name=$host \
                        --droonga-engine-host-name=$host \
                        --cache-size=-1 \
                        --daemon \
                        --pid-file=$PWD/droonga-http-server.pid

この時点で、2つの別々のDroongaクラスタが存在するようになりました。

この事は、各ノード上にあるステータスファイル live-nodes.json を見ると確認できます:

(on 192.168.0.10, 192.168.0.11)
# cat ~/droonga/state/live-nodes.json
{
  "192.168.0.10:10031/droonga": {
    "serfAddress": "192.168.100.52:7946"
  },
  "192.168.0.11:10031/droonga": {
    "serfAddress": "192.168.100.50:7946"
  }
}
(on 192.168.0.12)
# cat ~/droonga/state/live-nodes.json
{
  "192.168.0.12:10031/droonga": {
    "serfAddress": "192.168.100.51:7946"
  }
}

書き込みを伴うリクエストの流入を一時的に停止する

クラスタ alpha とクラスタ beta のデータを完全に同期する必要があるので、データの複製を始める前に、クラスタ alphaへのデータの書き込みを行うリクエストの流入を一時停止する必要があります。 そうしないと、新しく追加したreplicaが中途半端なデータしか持たない状態となってしまいます。 replica同士の内容に矛盾があると、リクエストに対してクラスタが返す処理結果が不安定になります。

データの書き込みを伴うリクエストとは、具体的には、クラスタ内のデータを変更する以下のコマンドです:

cronjobとして実行されるバッチスクリプトによって load コマンド経由で新しいデータを投入している場合は、cronjobを停止して下さい。 クローラが add コマンド経由で新しいデータを投入している場合は、クローラを停止して下さい。 あるいは、クローラやローダーとクラスタの間にFluentdを置いてバッファとして利用しているのであれば、バッファからのメッセージ出力を停止して下さい。

前項から順番にチュートリアルを読んでいる場合、クラスタに流入しているリクエストはありませんので、ここでは特に何もする必要はありません。

既存のクラスタから新しいreplicaへデータを複製する

クラスタ alpha からクラスタ beta へデータを複製します。 これは drndumpdroonga-request の各コマンドを使って行います。 (Gemパッケージ drndumpdroonga-client をあらかじめインストールしておいて下さい。)

(on 192.168.0.12)
# drndump --host=192.168.0.10 \
          --receiver-host=192.168.0.12 | \
    droonga-request --host=192.168.0.12 \
                    --receiver-host=192.168.0.12

--receiver-host オプションに作業マシン自身のホスト名またはIPアドレスを指定しておく必要がある事に注意して下さい。 ノード 192.168.0.11 の上で作業する場合であれば、コマンド列は以下の通りです:

(on 192.168.0.11)
# drndump --host=192.168.0.10 \
          --receiver-host=192.168.0.11 | \
    droonga-request --host=192.168.0.12 \
                    --receiver-host=192.168.0.11

新しいreplicaをクラスタに参加させる

データを正しく複製できたら、新しいreplicaを既存のクラスタに参加させます。 新たにクラスタに参加するノード 192.168.0.12 上で、すべてノードを --hosts オプションに指定して catalog.json を再作成してください:

(on 192.168.0.12)
# droonga-engine-catalog-generate --hosts=192.168.0.10,192.168.0.11,192.168.0.12 \
                                  --output=~/droonga/catalog.json

すると、サーバのプロセスが新しい catalog.json を検知して、自動的に自分自身を再起動させます。

この時点で、理論上、部分的に重なり合う2つのDroongaクラスタが存在するようになりました。

この事は、各ノード上にあるステータスファイル live-nodes.json を見ると確認できます:

(on 192.168.0.10, 192.168.0.11)
# cat ~/droonga/state/live-nodes.json
{
  "192.168.0.10:10031/droonga": {
    "serfAddress": "192.168.100.52:7946"
  },
  "192.168.0.11:10031/droonga": {
    "serfAddress": "192.168.100.50:7946"
  }
}
(on 192.168.0.12)
# cat ~/droonga/state/live-nodes.json
{
  "192.168.0.10:10031/droonga": {
    "serfAddress": "192.168.100.52:7946"
  },
  "192.168.0.11:10031/droonga": {
    "serfAddress": "192.168.100.50:7946"
  },
  "192.168.0.12:10031/droonga": {
    "serfAddress": "192.168.100.51:7946"
  }
}

「beta」と仮称した一時的なクラスタが姿を消している事に注意してください。 この時、新しいノード 192.168.0.12 はクラスタ charlie が3つのノードを含んでいる事を知っていますが、他の2つの既存のノードはその事を知りません。 既存の2つのノードは、自分自身が属しているクラスタ内にいるノードは2つだけだと認識しているため、流入してきたリクエストは、新しいノード 192.168.0.12 へはまだ配送されません。

次に、新しい catalog.json192.168.0.12 から他のノードにコピーします。

(on 192.168.0.12)
# scp ~/droonga/catalog.json 192.168.0.10:~/droonga/
# scp ~/droonga/catalog.json 192.168.0.11:~/droonga/

コピー先のノードのサーバが新しい catalog.json を認識して、自動的に再起動します。

この時点で、Droongaクラスタは1つだけ存在する状態となっています。

この事は、各ノード上にあるステータスファイル live-nodes.json を見ると確認できます:

(on 192.168.0.10, 192.168.0.11, 192.168.0.12)
# cat ~/droonga/state/live-nodes.json
{
  "192.168.0.10:10031/droonga": {
    "serfAddress": "192.168.100.52:7946"
  },
  "192.168.0.11:10031/droonga": {
    "serfAddress": "192.168.100.50:7946"
  },
  "192.168.0.12:10031/droonga": {
    "serfAddress": "192.168.100.51:7946"
  }
}

「alpha」と仮称した古いクラスタが姿を消している事に注意してください。 この時、2つのreplicaからなる古いクラスタの代わりとして、新しいクラスタ「charlie」は3つのreplicaのもとで完璧に動作しています。

書き込みを伴うリクエストの流入を再開する

さて、準備ができました。 すべてのreplicaは完全に同期した状態となっているので、このクラスタはリクエストを安定して処理できます。 cronjobを有効化する、クローラの動作を再開する、バッファからのメッセージ送出を再開する、などの操作を行って、クラスタ内のデータを変更するリクエストの流入を再開して下さい。

以上で、Droongaクラスタに新しいreplicaノードを無事参加させる事ができました。

既存のクラスタからreplicaノードを削除する

Droongaノードは、メモリ不足、ディスク容量不足、ハードウェア障害など、様々な致命的な理由によって動作しなくなり得ます。 Droongaクラスタ内のノードは互いに監視しあっており、動作しなくなったノードに対してはメッセージの配送を自動的に停止して、動作しないノードがあってもクラスタ全体としては動作し続けるようになっています。 このような時には、動作していないノードを取り除く必要があります。

もちろん、他の目的に転用したいといった理由から、正常動作中のノードを取り除きたいと考える場合もあるでしょう。

ここでは、192.168.0.10192.168.0.11、および 192.168.0.12 の3つのreplicaノードからなるDroongaクラスタが存在していて、最後のノード 192.168.0.12 をクラスタから取り除こうとしていると仮定します。

既存のreplicaをクラスタから分離する

既存のクラスタからreplicaノードを取り除くには、単に、そのノードを含まないreplicaノードのリストを伴ってcatalog.json を更新するだけでよいです:

(on 192.168.0.10)
# droonga-engine-catalog-generate --hosts=192.168.0.10,192.168.0.11 \
                                  --output=~/droonga/catalog.json

この時点で、理論上、部分的に重なり合う2つのDroongaクラスタが存在するようになりました。

新しい catalog.json を持つノード 192.168.0.10 は、クラスタ delta が2つしかノードを含んでいない事を知っています。ですので、流入してくるメッセージは、既に存在しないノード 192.168.0.12 へは配送されません。

次に、新しい catalog.json192.168.0.10 から他のノードへコピーします。

(on 192.168.0.10)
# scp ~/droonga/catalog.json 192.168.0.11:~/droonga/
# scp ~/droonga/catalog.json 192.168.0.12:~/droonga/

この時点で、Droongaクラスタは1つだけ存在する状態となっています。

192.168.0.11192.168.0.12 の両方がそれぞれリクエストを受け取ったとしても、それらのノードはリクエストをクラスタ delta 内のノードに対してのみ配送します。 取り残されたノード 192.168.0.12 へは、自分自身すらもリクエストを配送しません。

これで、ノードを取り除く準備ができました。 必要に応じて、サービスを停止させ、コンピュータを停止させましょう。

(on 192.168.0.12)
# kill $(cat ~/droonga/droonga-engine.pid)
# kill $(cat ~/droonga/droonga-http-server.pid)

クラスタ内の既存のreplicaノードを新しいreplicaノードで置き換える

ノードの置き換えは、上記の手順の組み合わせで行います。

192.168.0.10192.168.0.11 の2つのノードからなるDroongaクラスタがあり、ノード 192.168.0.11 の動作が不安定になっていて、これを新しいノード 192.168.0.12 で置き換えようとしていると仮定します。

既存のreplicaをクラスタから分離する

まず、不安定になっているノードを取り除きます。 取り除かれるノードを含まないように catalog.json を再作成して、クラスタ内の各ノードに展開します:

(on 192.168.0.10)
# droonga-engine-catalog-generate --hosts=192.168.0.10 \
                                  --output=~/droonga/catalog.json
# scp ~/droonga/catalog.json 192.168.0.11:~/droonga/

これで、ノード 192.168.0.11 がクラスタから無事に分離します。

新しいreplicaを追加する

次に、新しいreplicaをセットアップします。 1つのノードだけを含む仮のクラスタを作り、既存クラスタから新しいクラスタへデータを複製します:

(on 192.168.0.12)
# droonga-engine-catalog-generate --hosts=192.168.0.12 \
                                  --output=~/droonga/catalog.json
# drndump --host=192.168.0.10 \
          --receiver-host=192.168.0.12 | \
    droonga-request --host=192.168.0.12 \
                    --receiver-host=192.168.0.12

データの複製が完了したら、ノードをクラスタに参加させる準備は完了です。 catalog.json を再作成し、クラスタ内のすべてのノードにそれを複製します:

(on 192.168.0.12)
# droonga-engine-catalog-generate --hosts=192.168.0.10,192.168.0.12 \
                                  --output=~/droonga/catalog.json
# scp ~/droonga/catalog.json 192.168.0.10:~/droonga/

最終的に、192.168.0.10192.168.0.12 の2つのノードからなるDroongaクラスタができあがりました。

まとめ

このチュートリアルでは、既存のDroongaクラスタに新しいreplicaノードを追加する方法を学びました。 また、既存のreplicaを取り除く方法と、既存のreplicaを新しいreplicaで置き換える方法も学びました。