ひらめいた:P2P分散ファイルシステム

ちょっとひらめいた。


基本的なアイディアは、プライマリノードとセカンダリノードがまったく同じデータを持っていて、プライマリが落ちたときにセカンダリがプライマリに昇格するというごく普通なもの。ただし、プライマリが落ちたとき、プライマリでもセカンダリでもないノード全員が、昇格したプライマリに、「セカンダリいますか?」と聞きに行く。昇格したプライマリは、一番最初に受け取った「セカンダリいますか?」を送ってきたノードに、「セカンダリやれ」と送る。2つ目以降の「セカンダリいますか?」には「もういるよ」と返す。
これで次々にフェイルオーバーができる。それにノードの参加と離脱がないときには、プライマリが単なるファイルサーバーになるだけだから、ロックは特に問題なく処理できる。


あとはFUSEをかぶせれば分散ファイルシステムになる。


これだけでも良いのだけど、これだけだとプライマリノードとセカンダリノードにデータが集中してしまう。負荷が集中することはさほど問題ではないとしても、データが集中するのは、書き込みを全部RAMに行うVIVER的に良くない。それからフェイルオーバーするときに、次期セカンダリにデータをコピーするトラフィックが大きくなってしまう。


そこで、プライマリ/セカンダリをグループ分けする。各グループにそれぞれプライマリとセカンダリがいる。これでデータが分散できる。



というわけで、まず第一段階の実装。

  • 用語:
グループ
1つのグループには、1台のプライマリノードと、1台のセカンダリノードと、多数の参加ノードがいる。(ネットワーク全体の中にグループが2つあれば、プライマリノードは2台存在する)
ノード
複数のグループに参加することができる。
プライマリノード
グループのデータを管理しているノード。セカンダリノードとデータを同期している(セカンダリノードがいるとき)。書き込み要求(Write)と読み込み要求(Read)を受け付け、応答する。Writeの場合はセカンダリノードにもデータを送る(SyncPush)。
セカンダリノード
グループのプライマリノードとまったく同じデータを持っているノード。プライマリノードがダウンしたときに、プライマリノードに昇格する。
参加ノード
次期セカンダリノードの候補。
  • プロトコル:
Discovery
UDP。基本的にはブロードキャストする。ノードがネットワークに参加するときに、自分が参加するグループのプライマリノードのIPアドレスを得るために使う。送信するデータは、自分のIPアドレスと、参加したいグループ一覧。
DiscoveryACK
UDP。Discoveryを受け取ったときに、受信したグループ一覧の中に自分がプライマリノードをしているグループが入っていたら、DiscoveryACKをDiscoveryを送信してきたノードに送り返す。送信するデータは、自分のIPアドレスと、自分がプライマリノードをしているグループ名。プライマリノードをしているグループが複数あれば、DiscoveryACKを複数送り返す。
SyncPull
TCP。Discoveryでプライマリノードを見つけたら、そのプライマリノードに送る。SyncPullを受け取ったプライマリノードは、既にセカンダリノードがいるならそのセカンダリノードのIPアドレスを、いないなら自分が持っているデータを全部送る。送信するデータはグループ名。受信するデータはセカンダリノードがいるか否かと、いるならそのセカンダリノードのIPアドレス、いないならプライマリノードが持っているデータ。
Read
TCP。プライマリノードにのみ送信できる。送信するデータは、グループ名、要求するファイルのパス、ファイルのシーク位置、長さ。受信するデータは要求したデータ。
Write
TCP。プライマリノードにのみ送信できる。送信するデータは、グループ名、ファイルのパス、シーク位置、長さ、データ。受信するデータは成功またはエラー。
SyncPush
TCP。プライマリノードがWriteを受け取ったとき、プライマリノードがセカンダリノードに送信する。送信するデータは、グループ名、ファイルのパス、シーク位置、長さ、データ。できれば2相コミットでトランザクション管理する。

V-FIELDと連携して使うので、JoinやNotify Upなどは無くて良い。ノードの参加や離脱もV-FIELDから教えてもらえる(教えてもらえるようにV-FIELDを改良する)。




以上が第1段階。第2段階は、動的にグループに参加したり離脱したりできるようにして、グループ分けを自動で行う。第3段階では、FUSEをかぶせてファイルシステムにする。第1段階でファイルシステムにするには、トップレベルのディレクトリを書き込み不可にして、トップレベルにグループ名と同じディレクトリを置く。



RUNESでは、適用するプラグイン名と同じ名前のグループに属することにする。(プラグイン名がUDPのパケット1つに収まらないとDiscoveryで問題が発生しそう…)
これでロック付きのクラス変数がサポートできる。FUSEをかぶせれば、DHCPサーバーのリースファイル(dhcpd.leases)をこのP2P分散ファイルシステムに直接置いたりと、データの引き継ぎが簡単に(と言うか何も考えないでも)できるようになる。データベースのデータ本体をこのP2P分散ファイルシステムに置けば、レプリケーションが簡単にできる。Webサーバーのデータをこのファイルシステムに置けば、CGIを含んだ動的なWebサービスでも簡単にデータが引き継げる。


このP2P分散ファイルシステムは、フェイルオーバーに必要な機能の内で、データの引き継ぎしかやらないけど、これにkeepalivedを組み合わせれば、IPアドレスの引き継ぎとサーバーの起動もできる。keepalivedが提供する機能の内、ホストがダウンしたことの通知はV-FIELDを使ってできるので、RUNESがノードの参加と離脱をプラグインに通知するようにすれば、プラグイン側で必要のはvrrpdだけで良くなる。さらにRUNESメッセージバスに対応したvrrpdプラグインを作ってしまえば、フェイルオーバーに対応したプラグインは簡単に作れるようになると。


VIVER+V-FIELDと連携するというのが結構ポイント。V-FIELDはVIVERでディスクイメージを共有するのに使うので、ディスクIOが発生するごとにノード一覧の更新が行われる。なのでノード一覧をメンテナンスするために余分なトラフィックが発生しない。

うーむ。V-FIELDは使える。



なんか、このP2P分散ファイルシステム単体で十分使えるんじゃないか。VIVER要らない?(笑




V-FIELDが要らないんじゃないかともふと思うかもしれないけど、V-FIELDはブロックデバイスレベルなので、レイヤーが違って住み分けになる。ファイルシステムレベルだとデータの単位がファイルになるので、分散の粒度が大きくなってしまう。このP2P分散ファイルシステムは、巨大ファイルがどーんとあるような場合は、まったく分散されない。(メタデータとデータを別にすればこの分散も可能。ただ実装が複雑になる。メタデータとデータを別にしているのがLustreだったりGfarmFSだったりするけど、これらはデータの分散とカスケード・フェイルオーバー、ノードの動的な追加の機能が無い)
それからV-FIELDは最初から書き込みが無いこと前提にしているので、キャッシュの更新を無視できて、Read要求を多数のノードに並列して行える。よって速い。(V-FIELDに書き込みサポートを追加しようとすると、キャッシュの更新を同期的に行わないといけなくて、スケーラビリティが非常に低くなってしまう。Readは高速だけどWriteは遅い)


最高なのは、データをV-FIELD的に分散して、メタデータをこのP2P分散ファイルシステム的に管理する方法か。Readは高速でWriteは普通。ただ多重化度に応じてWrite時のトラフィックが増えるのはしょうがないか。プライマリから1台目のセカンダリにデータを送るときは同期的にやって、2台目以降は非同期にすれば、連続書き換え時のトラフィックは減らせる&高速化できる。


V-FIELDの分散アルゴリズムもいろいろと思うところがあったりして。今はとにかく多重化度を平均化するだけ。これはまた書く。