Droongaクラスタにノンストップでノードを追加する手順
ノードを追加する操作において想定されるワークフローを検討する。
- node0/192.168.100.50
- node1/192.168.100.51
の2つのレプリカがあると仮定する。 ここに
- node2/192.168.100.52
を追加する。またこの時、
- node2/192.168.100.51
を複製元として使う。
ノンストップでもストップ有りでも必要な機能:
- graceful restart:古いプロセスは、新たなメッセージの受け取りは締め切るが、その時点までに受け取っていたメッセージについてはすべて処理を完了させてから、完全にプロセスを終了する。新しいプロセスは、すぐにメッセージの受け取りと処理を始める。→現在は可能。
- droonga-replicate コマンド
- droonga-requestのstdin対応 →これは既に可能。
ノンストップでの追加のために必要な機能:
- graceful stop:新たなメッセージの受け取りは締め切るが、その時点までに受け取っていたメッセージについてはすべて処理を完了させてから、完全にプロセスを終了する。→現在は可能。
- last message time保持、graceful stop後に出力
- effective message time保持、セット
- バッファ機能→可能となっているはず。
serfを利用したクラスタの死活監視の仕組みについて、以下の挙動になるよう変更を行っておく。
- serfの各ノードは、change_portイベントまたはクエリを受信すると、serfのポート番号を変更する。(これは、live nodesが認識される先のクラスタを分けるため。)
droonga-engineは以下の挙動になるよう変更を行っておく。
- 新しいcatalog.jsonが監視対象ディレクトリ(staging-catalog)以下に配置されたら、即座にそれを検知する。
- effectiveDateが現在時刻より前であれば、監視対象ディレクトリに置かれたstagingなcatalog.jsonの内容を、メモリ上のcatalog.jsonの内容に即座に反映する。と同時に、ファイルへの書き込み権限がある場合は、本番のcatalog.jsonにも変更を反映する。
もしくは、serfのメッセージを使って「新しいcatalogはここにあるよ」と通知、ダウンロードさせる仕組み。
- droonga-engineは、最後に自分が受け取ったメッセージのtimestampを保持する。
(last_message_timestamp)
- この情報はメモリ上の揮発性の情報として保持する。
- プロセス終了時に、不揮発性の情報として出力する。
- droonga-engineは、「これ以後のtimestampのメッセージのみ有効と見なす」「これ以前のtimestampのメッセージが来ても無視する」と判断する基準となるtimestampを保持できる。
(effective_message_timestamp:初期値=nil)
- この情報は不揮発性の情報として与えられ、読み込んだ後はメモリ上の揮発性の情報として保持する。
- effective_message_timestampが設定されている場合、入ってきたメッセージのtimestampをチェックして、古いメッセージであれば破棄する。
ノードの状態に応じて挙動を帰るバッファを実装する。
- readなメッセージが来た場合、生存ノード同士だけで配送先を決める。
- writeなメッセージが来た場合、「配送するだけで、結果を待たない」という設定のメッセージを、生死に関わらずすべてのノードに送る。
- 死んでいるノードへ配送するように指示されたメッセージは、内蔵のバッファに溜め込まれる。
- node2をクラスタに仮追加する。
- node1をクラスタから切り離す。
- node1からnode2へデータを複製する。
- node1, node2を元のクラスタに戻す。
複製作業に関わらないノード(生存ノード、ここではnode0のみ)のcatalog.jsonに、node2の情報を加えて、最終的な構成の状態にあたるcatalog.jsonを用意する。
node0% droonga-catalog-modify-replicas --dataset=Starbucks \
--add-hosts=192.168.100.52 \
--remove-hosts="" \
--source=~/droonga/catalog.json \
--output=~/tmp/catalog.json
node0% scp catalog.json 192.168.100.52:/tmp/
node2% echo 7375 > ~/droonga/state/serf_port
node2% droonga-catalog-modify-replicas --dataset=Starbucks \
--add-hosts="" \
--remove-hosts="192.168.100.50,192.168.100.51" \
--source=/tmp/catalog.json \
--output=~/droonga/staging-catalog/catalog.json
node0% cp /tmp/catalog.json ~/droonga/staging-catalog/
この時点で、
- node2はserfの監視ポートが違う(7375)ので、元のクラスタから見たらノードとして動作しない。
- node2は、node0, node1から見た時は死んだノードとして扱われるようになる。
- node2自身から見た時は、node2だけのクラスタとなっている。
- 生存ノードからnode2へ配送される予定だったwriteなメッセージが、バッファに溜まり始める。
node1のcatalog.jsonをクラスタから切り離す。
node1% droonga-graceful-stop --output=~/droonga/last-message-timestamp
node1のノード構成を変更する。
node1% echo 7374 > ~/droonga/state/serf_port
node1% droonga-catalog-modify-replicas --dataset=Starbucks \
--add-hosts="" \
--remove-hosts=192.168.100.50,192.168.100.52 \
--source=~/droonga/catalog.json \
--output=~/droonga/staging-catalog/catalog.json
この時点で、
- node1はserfの監視ポートが違う(7374)ので、元のクラスタから見たらノードとして動作しない。
- node1は、node0から見た時は死んだノードとして扱われるようになる。
- node1自身から見た時は、node1だけのクラスタとなっている。
- node0からnode1へ配送される予定だったwriteなメッセージが、バッファに溜まり始める。
drndumpでデータを複製する。
node1% drndump --host=192.168.100.51 \
--dataset=Starbacks | \
droonga-client --host=192.168.100.52
※step2, step3を1操作で行うコマンドの案
node1% droonga-replicate --from-host=192.168.100.51 \
--from-port=10031 \
--to-host=192.168.100.52 \
--to-port=10031 \
--datasets=Starbacks \
--tag=starbacks
データの複製が終わったら、node1のlast_message_timestampをnode2のeffective_message_timestampに設定する。
node1% timestamp=$(droonga-get-status --name=last_message_timestamp)
node2% droonga-set-status --name=effective_message_timestamp --value=$timestamp
最終的な構成のクラスタのためのcatalog.jsonをnode1, node2に展開する。
node0% scp catalog.json 192.168.100.51:~/droonga/staging-catalog/
node0% scp catalog.json 192.168.100.52:~/droonga/staging-catalog/
droonga-engineが自動的に新しいcatalog.jsonを認識する。
しかし、node1とnode2はserfのポート番号が違うため、まだ生存ノードから見たら死んだノード扱いになっている。 そこで、serfのポート番号を元に戻す。
node1% echo 7373 > ~/droonga/state/serf_port
node2% echo 7373 > ~/droonga/state/serf_port
ここで、node1とnode2は、生存ノードから見た時に、ステータスが「死んでいるノード」から「復帰中のノード」に切り替わる。
- 生存ノードはバッファに溜め込んでおいたwriteなメッセージを、復帰中のノード(node1とnode2)に配送し始める。
- node2は、effective_message_timestampに基づいて、バッファから配送されてきたメッセージのうち、node1には渡されていないがnode2には渡されていたというメッセージ(=すでにdrndumpを通じてnode1から結果を受け取っているメッセージ)を破棄する。
- バッファが空になるまでは、復帰中のノード宛のメッセージはバッファに貯まっていく。
- バッファが空になったら、復帰中のノードは「生きている」状態に戻る。
- 生存ノードの状態に戻った後は、通常通りメッセージが流れてくるようになる。
- 「有効期限切れと見なすメッセージの時刻」はもう不要なので、破棄する。
以上でノードの追加は完了である。