メタデータサーバ冗長化解説
==========================

冗長化の仕組み
--------------

概要
~~~~

  　メタデータサーバの冗長化は異なるノード上に構築された複数のgfmd間でメタデー
  タをレプリケーションすることで実現する。現時点では1:1構成にのみ対応している。
  　冗長化構成のgfmdには2種類の役割があり、それぞれマスターgfmd、スレーブgfmdと
  呼ぶ。マスターgfmdは冗長化構成において1つのみ存在する。クライアントのリクエ
  ストを受け付けて、メタデータの更新情報をスレーブgfmdへ送信する。
  　スレーブgfmdは冗長化構成において現時点では1つのみ検証されているが、設計上は
  複数存在することができる。スレーブgfmdはクライアントのリクエストを一切受け付
  けず、gfsdからの接続も受け付けない。
  　マスターgfmd・スレーブgfmdの切り替えは現時点では自動的には行われない。
  　クライアントとgfsdはgfmdとの通信が切断すると、常に冗長化構成内のgfmd全てへ
  接続を試みて、レスポンスのあったgfmdへ接続する。マスター・スレーブの切り替え
  が適切に行われていれば、常に1つのマスターgfmdだけがレスポンスを返す。

  * 将来的な設計

    　現状マスターgfmdのみクライアントのリクエストを処理する。将来的には負荷分
    散のためにスレーブgfmdにも一部のリクエストが処理できるようになることが望ま
    しい。シングルマスターのシステムにおいては、データベースを変更しないread
    onlyな操作のみ スレーブgfmdが処理できるはずである。
    　現時点では更新情報のソースは1つのマスターgfmdであるシングルマスターのシス
    テムである。将来的にマルチマスターに対応すると、マスターgfmd同士互いの更新
    情報を送受信するようになる。
  
対応可能・不可能な障害
~~~~~~~~~~~~~~~~~~~~~~

  * 以下の障害に対して、一部の例外を除きメタデータが保護される。

    - マスター/スレーブgfmdノードのいずれかが突然停止した場合。

    - マスター/スレーブgfmdノードのいずれかがネットワークアクセス不能になった
      場合。

    - マスター/スレーブgfmdノードのいずれかがデータベースへアクセス不能になった
      場合。

  * 以下の障害に対しては、メタデータが保護されるとは限らない。

    - マスター/スレーブgfmdノードがほぼ同時に停止した場合。


更新情報ジャーナルファイルとマスターデータの世代番号
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  　メタデータの更新情報には更新情報シーケンス番号が付与される。このシーケンス
  番号は更新情報ジャーナルファイルへ各更新情報と一緒に保存され、その後データベ
  ースの世代番号として、データベースに保存される。
  　シーケンス番号はマスターデータ(マスターgfmd上のメタデータ)とスレーブデータ
  (スレーブgfmd上のメタデータ)の間で一貫性を保つための鍵となる情報である。

  　ジャーナルファイルは循環して書き込まれる。すなわちファイルが一定サイズに達
  すると、先頭のヘッダ部の直後から前に書き込んだ情報を上書きして保存する。
  　更新情報は1つのオブジェクトに対する変更について1レコードである。また、トラ
  ンザクションの開始・終了も各1レコードとして記録する。ジャーナルファイルへの書
  き込みは、トランザクション単位に行われる。トランザクション開始〜トランザクシ
  ョン終了までの複数のレコードをファイルへ書き込んだ後、fdatasyncシステムコール
  を呼び、物理ディスクへ反映する。設定によってはfdatasyncを呼ばないようにするこ
  ともできるが、障害発生時にOSのファイルキャッシュ上にあるデータは失われる。

  * 将来的な設計

    　シングルマスターでは単一のマスターのみがシーケンス番号が割り振ら
    れるが、マルチマスターの場合は、複数マスターそれぞれが別系列のシー
    ケンス番号を割り当てるアイディアがある。この場合、inodeごとに所有権
    を管理し、変更については、所有権のあるマスターに依頼することとする。


レプリケーション方式の種類
~~~~~~~~~~~~~~~~~~~~~~~~~~

  　ジャーナルファイルに永続化された更新情報をマスターgfmdから
  スレーブgfmdへと送信する方式として、次の２種類を実装した。

  (1) 同期レプリケーション

    　レスポンス性能よりも、耐障害性能を重視した方式。同期レプリケーションは、
    クライアントから更新操作を受信したとき、更新情報の永続化と複数のスレーブ
    gfmdへの更新情報伝達を並列に実行し、それらの処理が完了するまで待機してから
    クライアントへ結果を返信する。この方法を用いることで、マスターgfmdプロセス
    が突然終了した場合でも、終了直前までに実行されたクライアントからの更新操作
    によるメタデータの変更は、スレーブgfmdが保持しているため保護される。ただし
    、マスターgfmdプロセスとスレーブgfmdプロセスがほぼ同時に終了した場合は保護
    されない。gfmd間通信チャンネルの経路に障害が発生した場合は、更新最中のデー
    タはマスターのみに保存される。
    　gfmd間通信チャンネルが切断状態にあるスレーブgfmdに対しては更新情報を送信
    できないため、ジャーナルファイル上の未送信の更新情報が保存された個所へのフ
    ァイルポインタを記憶しておき、接続確立の直後に未送信の更新情報をまとめて送
    信する。接続確立の直後の更新情報送信については、未送信の情報を送信し終える
    までは、同期レプリケーションを選択している場合においても非同期レプリケーショ
    ンと同じ方法で送信する。

  (2) 非同期レプリケーション

    　耐障害性よりもレスポンス性能を重視した方式。非同期レプリケーションを用い
    た場合、クライアントからの更新操作を受信すると、更新情報を永続化してクライ
    アントへ結果を返信する。スレーブgfmdでは受信データをそのままジャーナルファ
    イルへ保存する。スレーブgfmdは更新情報受信処理とは非同期で、永続化ファイル
    の更新情報をデータベースとメモリへ反映する。スレーブgfmdへの更新情報送信は、
    更新伝達送信用の常駐スレッドにより行われる。常駐スレッドは複数のスレーブ
    gfmdに対して順番に更新情報を伝達する。現時点ではgfmd channelの非同期性を生
    かしていないので、各gfmdにはシーケンシャルに送信される(当然1:1構成において
    は影響はない)。同期レプリケーションでは一度に送信する更新情報は１トランザク
    ションごとであるが、非同期レプリケーションでは複数のトランザクションの情報
    を一定量まとめて送信する。

クラスタの概念
~~~~~~~~~~~~~~

    gfmdクラスタはgfmdの集合である。gfmdは必ず1つのgfmdクラスタに属する。ネット
  ワークトポロジ的に近い位置にあるgfmdを同一のクラスタに設定する使い方を想定。
    マスターgfmdと同一クラスタに属するスレーブgfmdへは同期レプリケーション、
  それ以外のスレーブgfmdへは非同期レプリーションが自動的に選択される。マスター
  が別のクラスタのgfmdに切り替わった場合、同期レプリケーションのクラスタ・非同期
  レプリケーションのクラスタも切り替わる。

マスター・スレーブの切り替え
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  　マスターgfmdとスレーブgfmdでスプリットブレイン問題を解決するための調停は行っ
  ていない。設定ファイルとシグナルによる制御のみである。gfmdの役割（マスター
  またはスレーブ）の切り替えについての実装は次のとおりである。
  
  - gfmdをマスターとして起動するか、スレーブとして起動するかは設定ファイルの内
    容によって決まる。
  
  - スレーブgfmdは自動的にはマスターデータサーバへ切り替わらない。シグナル
    SIGUSR1をスレーブgfmdプロセスに送信することで、マスターgfmdへ昇格する。 

  - マスターgfmdはスレーブgfmdへ降格することはない。クライアントとgfsdは後述の
    再接続機能により、マスターへ昇格した後の元スレーブgfmdへ接続する。

  * 将来的な設計

    　マスター・スレーブは直接調停を行わないのは、外部の監視エージェン
    トがマスター・スレーブの切り替え制御を行う方式を採用することを見込
    んでいるためである。将来は、監視エージェントなしで、gfmd間の多数決
    プロトコルにより、自律的にマスターを切替える機能を提供することが考
    えられる。

メタデータの一貫性
~~~~~~~~~~~~~~~~~~

  * 永続化ファイルとデータベース間の一貫性

    　各更新情報に連続的なシーケンス番号を付与する。このシーケンス番号
    は、ジャーナルファイルとバックエンド・データベースの両方へ記録する。
    gfmd を再起動した時に、ジャーナルからデータベースへ、未適用の更新情
    報の反映を行なうが、データベースに記録されたシーケンス番号は、この
    未反映の更新情報を示す値となる。バックエンド・データベースとして関
    係データベース (PostgreSQL) を用いている場合、更新情報のデータベー
    スへの記録と、シーケンス番号のデータベースへの記録は、データベース
    的に同一のトランザクション内で行なう。これにより、既に適用した更新
    を誤って適用し直すようなことがないことを保証している。シーケンス番
    号テーブルはメタデータマスター1つに対して、1レコードが対応する。現
    時点ではシングルマスターなので、シーケンス番号用のテーブルに保存さ
    れるレコードは１つである。

  * メモリとジャーナルファイル間の一貫性

    　マスターgfmdにおいては、メモリとジャーナルファイルの間で一貫性が損なわれ
    る可能性があり、今回の対応作業では制限事項とした。今回の対応作業を行う前の
    、メモリとデータベース間の一貫性に関する問題と同様である。一貫性が損なわれ
    るのは、ジャーナルファイルへの更新情報の保存が失敗した場合である。永続ファ
    イルへの保存を行う前にメモリ上ではメタデータが変更されており、保存に失敗し
    た際にメモリ上のデータが変更されたまま残るためである。ジャーナルファイルへ
    の保存に失敗した場合はマスターgfmdを停止して、スレーブgfmdが昇格するべきで
    ある。

  * マスターとスレーブ間の一貫性

    　まず最初に、システムを稼働する前にマスターのデータベースを、スレーブの
    データベースへコピーすることを想定している。コピーした後は、更新情報伝達の
    設定が同期伝達である場合に限り、後述の制限を除いて、マスター・スレーブ間の
    一貫性が確保される。マスターgfmdに障害が発生して、スレーブgfmdをマスター
    gfmdへ昇格した場合、後続するクライアントからの更新操作による変更は、元スレ
    ーブのジャーナルファイル及びデータベースに保存される。再び元マスターgfmdを
    起動するには、次の手順を行う。

    (1) 起動時に指定する設定ファイルを切り替えることで、マスターgfmdをスレーブ
        gfmdとして起動する。

    (2) 元スレーブgfmdから更新情報を全て受信した後に、シグナルでマスターgfmdへ
        昇格する。

    　上記の(2)については２つの問題が存在する。一つは元スレーブgfmdに対して行わ
    れたメタデータの変更が、全て元マスターgfmdへ送信されたかどうかを外部から容
    易に判断することができない。そのため昇格する前に十分な時間が経過している必
    要がある。
    　もう1つは元スレーブgfmdのジャーナルファイルに追加された更新情報のサイズ
    が、ジャーナルファイルのサイズを一周して古い更新情報が書きつぶされることであ
    る。この場合、元マスターgfmdをスレーブとして起動しても、更新情報伝達は行わ
    れない。更新情報伝達が行われない場合は、一旦全てのgfmdを停止して、データベー
    スのコピーとジャーナルファイルの削除を実施する必要がある。

    　なお、2つ以上のgfmdをマスターgfmdとして起動することができてしまうが、
    メタデータおよびジャーナルファイルの一貫性が損なわれることに注意。

  * gfmdとgfsd間の一貫性

    　クライアントがGfarm上のファイルへwrite openして、ファイルを閉じる前
    にマスターgfmdが停止すると、gfsdに保存されているGfarmファイルの状態と、メタ
    データの間の一貫性が損なわれる。この問題はメタデータ冗長化を利用しない場合
    にも存在する。

  * 将来的な設計

    　バックエンド・データベースの手動コピーに頼らず、gfmdのみで無停止
    で全メタデータをレプリケーションする機能は重要度の高い機能である。
    　また、マスターおよびスレーブがともに動作している状態で、レプリケー
    ションの完了を待って、サービスの中断を伴わずにスレーブからマスター
    への切り替える機能も、無停止で機器交換を行なうような保守作業の際に
    有用と考えられる。
    　書き込み中のファイルのメタデータとメタデータが指すgfsd上のファイルの
    一貫性確保の方法については、競合状態を避けるのが難しいため、十分に
    検討する必要がある。

gfmd channel
~~~~~~~~~~~~

  　gfmd間の情報の送受信にはgfmd channelを使用する。gfmd channelはgfmd冗長化対
  応で追加した機能である。このチャンネルはスレーブgfmdからマスターgfmdへのTCP接
  続で、gfmd/gfsd間チャンネル（いわゆるback channel）と同様に、1つのチャンネル
  で双方向の通信が可能な非同期プロトコルである。
  　gfmd channel設計するに当たり、gfmd内部では冗長化クラスタを構成するgfmdノー
  ドを表すオブジェクトを導入した。現時点ではオブジェクトの基本情報は静的で、
  gfmdの設定ファイルに記述する方法を取っている。

  * 将来的な設計
  
    　現時点では非同期性を生かした実装にはなっておらず、将来的にメタデータのマ
    ルチマスター対応や負荷分散対応を実装する際には、非同期性を生かした実装に変
    更する必要がある。

冗長化構成におけるクライアント/gfsdの動作
-----------------------------------------

クライアント/gfsdからgfmdへの通信のフェイルオーバ
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  　クライアントとgfsdのgfmdへの接続ロジックは同じで、設定に従って複数のgfmdへ
  並列に接続を試みる。TCPソケットでlisten/acceptするのはマスターgfmdだけなので、
  常にマスターgfmdへ接続する。
  　マスターgfmdが停止して通信が切断した場合、クライアントとgfsdは接続を確立す
  るまで再接続を繰り返す。スレーブgfmdがマスターgfmdへ昇格するとTCPソケットの
  listen/acceptを開始するので接続が確立する。

  　切断したときの現象としては、gfarm2fs を通じて接続している場合、gfarm2fsと
  gfsdとの通信は接続したままなので、ファイルのread/writeの間はエラーが発生しな
  い。stat()やclose()を実行したときにgfmdとの通信が発生し、一度通信エラーが発生
  する。マスターgfmdが接続可能な状態であれば2度目以降は通信エラーは発生しない。
    "gfmdとgfsd間の一貫性"にも記述したとおり、write openしているときにgfmdが停
  止すると、メタデータとgfsd上のファイルに差異が生じてしまう可能性がある。

  * 将来的な設計

  　gfmdが切り替わっても、クライアントから透過的にGfarmファイルシステムへアク
  セスできるようにすることが目標である。現時点では切り替わった後のgfmdに接続し
  ても前に接続していたときに開いていたfdはgfmd上に存在しないため、新たに開く
  必要がある。例えば、クライアント側で開いていたファイルのパスを覚えておき、
  切断後にgfmdへアクセスする必要が出たときに切断を検知して、そのパスで開き直す。
  開き直したら、クライアント内の古いfdは置き換える必要がある。次に、gfsdも
  古いfdを持っているため、クライアントが開き直した新しいfdへ置き換える必要が
  ある。以上の処理はアイディアの段階に過ぎず、race conditionを回避したり、
  gfsdがgfmdへ接続できなかった場合などのケースに対応する必要がある。

gfmdとgfsdの関係
~~~~~~~~~~~~~~~~

  　gfsdはマスターgfmdのみに接続する。gfsdは起動後およびマスターgfmdとの接続が
  切断時、マスターgfmdおよびスレーブgfmdへ接続を試みる。マスターとスレーブの切
  り替えが適切に行われていれば、スレーブgfmdがマスターgfmdに昇格したとき、gfsd
  は元スレーブgfmdへ接続することとなる。

  * 将来的な設計

    　将来的には負荷分散のためにマスターgfmdだけでなくスレーブgfmdにもgfsdを接
    続させるアイディアがある。その場合、gfmdとgfsdの従属関係を管理するための情
    報が必要になる。従属関係を参照してあるgfmdが受け付けたクライアントのリクエ
    ストを別のgfmdへフォワードすることがあるかもしれない。

実装に関するポイント解説
------------------------

ジャーナルファイルの書式
~~~~~~~~~~~~~~~~~~~~~~~~

  * ファイル全体

           +-----------+------+------+-----+------+
           |File Header|Record|Record|. . .|000...| 
           +-----------+------+------+-----+------+

   一定のファイルサイズまでRecordが分断されないように書き込み、
   ファイル末尾は0で埋める。

  * File Header
           +--------+----------+-------------+
           |MAGIC(4)|VERSION(4)|0000...(4088)|
           +--------+----------+-------------+
   offset  0        4          8          4096

     - MAGIC : 先頭を示す文字列“GfMj”
     - VERSION : ジャーナルファイルフォーマットのバージョン番号
     - 9バイト目 から 4096 バイト目 : 予約領域

  * Record

           +--------+---------+---------+----------------+---------+--------+
           |MAGIC(4)|SEQNUM(8)|OPE_ID(4)|DATA_LENGTH(4)=n|OBJECT(n)|CRC32(4)|
           +--------+---------+---------+----------------+---------+--------+
   offset  0        4        12        16               20       20+n    24+n

   - MAGIC : 先頭を示す文字列 “GfMr”
   - SEQNUM : シーケンス番号
   - OPE_ID : 操作番号
   - DATA_LENGTH : OBJECT のデータ長
   - OBJECT : シリアライズされた更新情報
   - CRC32 : crc32によるチェックサム


メタデータ更新時のジャーナル関連の処理
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  * 同期レプリケーションの場合

    - master gfmd

      protocol_main():

        1) db_journal_enter()
        2) journal file へ更新情報を書き込む。
        3) GFM_JOURNAL_END の場合、4) へ。
	   異なる場合、return。
        4) 4-A), 4-B) のスレッドを生成。
          4-A) gfmdc_journal_file_sync_thread():
               journal file を fdatasync。
          4-B) gfmdc_journal_send_thread():
               slave gfmd へ journal record を送信。(GFM_PROTO_JOURNAL_SEND)
               送信するデータは mdhost ごとに保持している journal_file_reader
               により journal file から読み込む。
        5) 4-A), 4-B) のスレッド完了待ち。

      db_thread():

        1) journal file から更新情報を読み込む。
        2) 更新情報を DB へ書き込む。(db_store_ops = db_pgsql_ops の関数を呼ぶ)

      gfmdc_recv_thread_pool thread:

        1) GFM_PROTO_JOURNAL_SEND の result を受信。
        2) gfmdc_journal_send_thread が待機している cond へ signal を送る。
           (gfmdc_journal_send_closure.send_end_cond)

    - slave gfmd

      gfmdc_recv_thread_pool thread:

        1) GFM_PROTO_JOURNAL_SEND を受信。
        2) 受信データをjournal_recvqに追加。

      db_journal_recvq_thread():

        1) journal_recvq から次に反映するべき seqnum を持つ journal record を
	   取得。
        2) journal record を journal file へ書き込む。

      db_journal_apply_thread:

        1) ジャーナルファイルから更新情報を読み込む。
        2) GFM_JOURNAL_BEGIN から GFM_JOURNAL_END までをリストに追加する。
        3) リストに追加された更新情報を DB へ書き込む。
	   (db_store_ops=db_pgsql_ops の関数を呼ぶ)
        4) リストに追加された更新情報をメモリへ反映する。
	   (journal_apply_opsの関数を呼ぶ)

  * 非同期レプリケーションの場合

    - master gfmd

      protocol_main():

        1) db_journal_enter()
        2) journal file へ更新情報を書き込む。
        3) GFM_JOURNAL_END の場合、journal file に対して fdatasync を実行。

      db_thread():

        1) journal file から更新情報を読み込む。
        2) 更新情報を DB へ書き込む。(db_store_ops = db_pgsql_ops の関数を呼ぶ)

      gfmdc_journal_asyncsend_thread():

        1) slave gfmd へ約 8KB まで journal record を送信する。
	   送信するべき journal record が存在しない場合は何もしない。
	2) 全ての slave gfmd に対して 1) を繰り返す。
	3) 2) を繰り返す。

    - slave gfmd

      同期レプリケーションの場合と同様。

