Navigation Menu

Skip to content

Droongaクラスタにノンストップでノードを追加する手順

YUKI "Piro" Hiroshi edited this page Jun 26, 2014 · 21 revisions

ノードを追加する操作において想定されるワークフローを検討する。

前提

  • 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を利用したクラスタの死活監視の仕組みについて、以下の挙動になるよう変更を行っておく。

  • serfの各ノードは、change_portイベントまたはクエリを受信すると、serfのポート番号を変更する。(これは、live nodesが認識される先のクラスタを分けるため。)

catalog.jsonの更新

droonga-engineは以下の挙動になるよう変更を行っておく。

  • 新しいcatalog.jsonが監視対象ディレクトリ(staging-catalog)以下に配置されたら、即座にそれを検知する。
  • effectiveDateが現在時刻より前であれば、監視対象ディレクトリに置かれたstagingなcatalog.jsonの内容を、メモリ上のcatalog.jsonの内容に即座に反映する。と同時に、ファイルへの書き込み権限がある場合は、本番のcatalog.jsonにも変更を反映する。

もしくは、serfのメッセージを使って「新しいcatalogはここにあるよ」と通知、ダウンロードさせる仕組み。

最後に受け取ったメッセージのtimestamp

  • droonga-engineは、最後に自分が受け取ったメッセージのtimestampを保持する。 (last_message_timestamp)
    • この情報はメモリ上の揮発性の情報として保持する。
    • プロセス終了時に、不揮発性の情報として出力する。
  • droonga-engineは、「これ以後のtimestampのメッセージのみ有効と見なす」「これ以前のtimestampのメッセージが来ても無視する」と判断する基準となるtimestampを保持できる。 (effective_message_timestamp:初期値=nil)
    • この情報は不揮発性の情報として与えられ、読み込んだ後はメモリ上の揮発性の情報として保持する。
    • effective_message_timestampが設定されている場合、入ってきたメッセージのtimestampをチェックして、古いメッセージであれば破棄する。

バッファ

ノードの状態に応じて挙動を帰るバッファを実装する。

  • readなメッセージが来た場合、生存ノード同士だけで配送先を決める。
  • writeなメッセージが来た場合、「配送するだけで、結果を待たない」という設定のメッセージを、生死に関わらずすべてのノードに送る。
  • 死んでいるノードへ配送するように指示されたメッセージは、内蔵のバッファに溜め込まれる。

基本方針

  1. node2をクラスタに仮追加する。
  2. node1をクラスタから切り離す。
  3. node1からnode2へデータを複製する。
  4. node1, node2を元のクラスタに戻す。

step1: 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なメッセージが、バッファに溜まり始める。

step2: node1をクラスタから切り離す

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なメッセージが、バッファに溜まり始める。

step3: node1からnode2へデータを複製する。

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

step4: node1, node2を元のクラスタに戻す。

最終的な構成のクラスタのための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から結果を受け取っているメッセージ)を破棄する。
  • バッファが空になるまでは、復帰中のノード宛のメッセージはバッファに貯まっていく。
  • バッファが空になったら、復帰中のノードは「生きている」状態に戻る。
    • 生存ノードの状態に戻った後は、通常通りメッセージが流れてくるようになる。
    • 「有効期限切れと見なすメッセージの時刻」はもう不要なので、破棄する。

以上でノードの追加は完了である。