2012年2月22日水曜日

PHP FUSEのセグメンテーション違反とバスエラーについて

こんなコマンドを流すと、PHP FUSEが落ちます(ファイル数4000個くらい?)。

mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt & find mnt &

1撃で落ちることもあれば、10回くらいで落ちることもあります。ログの位置も微妙にずれていて、確実に特定できるわけではないんですが。

コチラを参考に改修したら、確かに落ちなくなりました。
https://github.com/fujimoto/php-fuse/issues/5

I made 3 changes: removed "convert_to_array_ex(entry);" and the "zval_ptr_dtor(tmp_type);" and "zval_ptr_dtor(tmp_ino);".


個人的メモ。

2012年2月21日火曜日

PHP FUSEで関数呼び出しにUIDとGIDを付与したい


前エントリの通り、ユーザーのUIDやGIDに応じた処理を実装するには、全関数呼び出しについて引数にユーザーのUIDとGIDを付与してやる必要があるようです。というわけで、また魔改造してみました。

■改造場所
php_fuse_call_methodの中で、付いてない引数を勝手にねつ造します。

■改造内容(強引ですが)
HashTable *function_table; の直後に
uid_t uid;
gid_t gid;
uid = fuse_get_context()->uid;
gid = fuse_get_context()->gid;
zval *arg_uid;
zval *arg_gid;
MAKE_STD_ZVAL(arg_uid);
ZVAL_LONG(arg_uid, uid);
MAKE_STD_ZVAL(arg_gid);
ZVAL_LONG(arg_gid, gid);

-zval ***params = emalloc(sizeof(zval**) * (param_count));
+zval ***params = emalloc(sizeof(zval**) * (param_count+2));

va_end(va_params); の直後に
*(params+param_count) = &arg_uid;
*(params+param_count+1) = &arg_gid;
param_count = param_count+2;

efree(params); の直後に
zval_ptr_dtor(&arg_uid);
zval_ptr_dtor(&arg_gid);

■使い方
こんな感じで、全関数でUID/GIDを取得できるようになりました。
(変更前)function mknod($path, $mode, $dev)
(変更後)function mknod($path, $mode, $dev, $uid, $gid)


全然テストしてませんけど、一応動いてるみたいなんで、これでいいやw

2012年2月20日月曜日

PHP FUSEでuidやgidを正しく反映するには


引き続きPHP FUSEを魔改造させていただいております。

PHP FUSEでファイルやディレクトリを新規作成すると、操作を発行したユーザーのUIDやGIDによらず、常にPHP FUSEの実行権限で作成されてしまいます。なぜなら、オーナー情報がPHP FUSEから通知されてこないし、FUSEが気を利かせて自動でchownを呼んでくれたりもしないからです。このままでは一般用途のファイルシステムを作るには難があります。そこで、新規作成系の操作の直後に、自動的かつ強制的にchownが呼び出されるようにPHP FUSEを改造してしまいました。


■改造個所
php_fuse_mknodの中
php_fuse_mkdirの中

■改造内容
以上の関数内の最後のreturn r;の直前に、以下の行を挿入。
php_fuse_chown(path, fuse_get_context()->uid, fuse_get_context()->gid);

失敗しても別に構いやしないんで、戻り値は捨ててしまえ(えー

■備考
symlinkとlinkについては、どう実装するかは検討の余地があると思う。リンク自体のUID/GIDをサポートしているシステムとそうでないシステムがあるし、PHPの挙動はドキュメントに書いてないので大分怪しい。勝手にchownを呼ぶより、ファイルを作成する系の関数に引数で渡してあげるようにしたほうが適切かも知れない。

よって以下は未実装のまま放置。
php_fuse_symlinkの中
php_fuse_linkの中
php_fuse_chown(path_from, fuse_get_context()->uid, fuse_get_context()->did);

■追記
読み出し制御したい場合、全ての関数に実装が必要ですよねぇ。

PHP FUSEのパフォーマンス計測(暫定)


PHP FUSEで、パススルーするだけのダミーファイルシステムを作ってみました。PHP FUSEを経由することによるオーバーヘッドを調べるべく、一応bonnie++でベンチしてみました。

■マウントオプション
allow_other,kernel_cacheを有効にしました。direct_ioを有効化するとシーケンシャルアクセスのブロックサイズが巨大化(4KB->120KB)して大幅に高速化(1.5倍速くらいに達するだろうか)するようですが、書き込み待ちや複数アクセス時のプチフリっぷりが気になったので、切りました。

■ベンチ結果まとめ
read/writeともに、ブロックサイズの大小によって挙動が大分変わるようです。シーケンシャルアクセスに関しては無視できる程度の影響であるようでした。CPUパワーは15%くらいでムンムン使っているものの、速度には影響せず。しかしブロックサイズが小さいとパフォーマンスダウンが激しいようです。特にseekの遅さの影響をモロに受けているようです。現状read&writeの度にfseekを発行しているんで、前回のoffset情報をもとに要不要を判定して余分なfseekを抑制すると、速くなったりしないかな? また今度やってみよう。

■具体的な結果:NFSマウント(From:CentOS5.7 DomU on CentOS5.7 / To:OpenIndianaのZFS)
Version  1.96       ------Sequential Output------ --Sequential Input- --Random-
Concurrency   1     -Per Chr- --Block-- -Rewrite- -Per Chr- --Block-- --Seeks--
Machine        Size K/sec %CP K/sec %CP K/sec %CP K/sec %CP K/sec %CP  /sec %CP
toreteru-dev-www 2G   575  98 33267   3 21715   4   835  99 40707   1  8868 175
Latency             29500us   20753ms   10807ms   19498us     438ms   83160us
Version  1.96       ------Sequential Create------ --------Random Create--------
toreteru-dev-www01. -Create-- --Read--- -Delete-- -Create-- --Read--- -Delete--
              files  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP
                 16    25   0  2650   1    48   0    25   0  2923   0    50   0
Latency               422ms   36661us     647ms     529ms    7140us     352ms

■具体的な結果:PHP FUSE経由でスルーパスしたもの(現実的なエラー対策とか講じてますけど)
Version  1.96       ------Sequential Output------ --Sequential Input- --Random-
Concurrency   1     -Per Chr- --Block-- -Rewrite- -Per Chr- --Block-- --Seeks--
Machine        Size K/sec %CP K/sec %CP K/sec %CP K/sec %CP K/sec %CP  /sec %CP
toreteru-dev-www 2G    13   5 33111   3 13880   1   763  97 37051   2  2205  12
Latency              1502ms    9371ms    6478ms   26108us     534ms   54831us
Version  1.96       ------Sequential Create------ --------Random Create--------
toreteru-dev-www01. -Create-- --Read--- -Delete-- -Create-- --Read--- -Delete--
              files  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP
                 16    23   0  1842   1    49   0    24   0  1879   0    49   0
Latency               499ms    2074us    5770ms     429ms   10118us     282ms

■備考メモ
実装上、最大のハマりどころは、getattrで32bit超えの値を返すとPHP FUSEの中でオーバーフローしてしまう件。これは前エントリの通り強引にパッチ当てで解消しました。

次なるハマりどころは、mknod関数の実装じゃないでしょうか。パススルーしようにも、PHPにはそのものジャストのmknod関数が存在せず。posix_mknodなる聞き慣れない関数でお茶を濁しましたが、使い方が合ってるのか、よくわかりません。引数がメジャーとマイナーに分かれてる件は、PHP FUSEから渡されてきたバイト列を上位下位で2等分して渡せばいいのかなと勝手に解釈。動いてるし、FIFOとか使わないから、まぁいいや。ファイルシステム周りはシステムコールが使えないといろいろ不安ですね。

その次のポイントは、openの実装でしょうか。read&writeのたびにfopenしてると途轍もなく遅いので、openでfopenしたファイルハンドルをメンバ変数配列で保持&ファイルディスクリプタ番号を自前で生成して返却(3〜255)、releaseでfclose、writeとreadとreleaseはpathは無視してファイルディスクリプタ番号を元にメンバ変数で保持したファイルハンドルを特定して作業するって寸法で実装しました。ファイルディスクリプタ番号は有限なので、空き番号を管理する仕組みも入れておきました。

Linux開発の本を読んでおいてよかったなぁ。

PHP FUSEで2GB超のファイル容量を正しく報告できない件について


激しく環境依存ぽいんで、アレですけれども。。

■検証環境
CentOS6.2 + FUSE 2.8.7

■現象
2GB超のファイルをlsしたりstatしたりするとオーバーフローっぽい値が表示される。

■修正内容(手抜き)
fuse.c
(修正前)int value = Z_LVAL_PP(entry);
(修正後)long value = Z_LVAL_PP(entry);

※2個所あります。だいたい前者だけで大丈夫だと思いますが。
PHP_FUSE_API int php_fuse_getattr(const char * path, struct stat * st) 内
PHP_FUSE_API int php_fuse_statfs(const char * path, struct statfs * st) 内

いかにも対症療法ですが、正しくはどうすればいいんでしょうね。。
Z_LVAL_Pも同様のようですが、とりあえず動いたんで、これで…^^; スミマセン


あ、あと、これもかな。。違うかな。。
(修正前)st->st_size = (size_t)value;
(修正後)st->st_size = (off_t)value;

2012年2月8日水曜日

ZFS-FUSE 0.7.0をCentOSに導入する


epelのrpmは0.6.9です。なんと0.7.0ではxattrが使える=GlusterFSで使えるようになったということなので、試してみたいと思いました。

ソースから入れると当然、パスやサービス登録がめちゃめちゃなので、epelのsrpmをお借りしてソースを最新に入れ替えるという方法で導入しときました。

以下手抜きメモになります。パスとかデタラメなので参考にされる方は読み解いてください;

yum install -y fuse-devel libattr-devel libaio-devel libacl-devel zlib-devel fuse-devel scons openssl-devel
yum install rpm-build

wget http://download.fedora.redhat.com/pub/epel/6/SRPMS/zfs-fuse-0.6.9-6.20100709git.el6.src.rpm
rpm -i zfs-fuse-0.6.9-6.20100709git.el6.src.rpm
cd rpmbuild/SOURCES
wget -c 'http://gitweb.zfs-fuse.net/?p=official;a=snapshot;h=maint;sf=tgz' -O 'zfs-fuse-0.7.0-snapshot.tar.gz'
rm zfs-fuse-0.6.9-snapshot.tar.gz
cd ../SPECS
vi zfs-fuse.spec

2,3c2,3
< Version:          0.6.9
< Release:          6.20100709git%{?dist}
---
> Version:          0.7.0
> Release:          7.20120208git%{?dist}
10c10
< #   wget -c 'http://gitweb.zfs-fuse.net/?p=official;a=snapshot;h=maint;sf=tgz' -O 'zfs-fuse-0.6.9-snapshot.tar.gz'
---
> #   wget -c 'http://gitweb.zfs-fuse.net/?p=official;a=snapshot;h=maint;sf=tgz' -O 'zfs-fuse-0.7.0-snapshot.tar.gz'
12c12
< #   make new-sources FILES="zfs-fuse-0.6.9-snapshot.tar.gz"
---
> #   make new-sources FILES="zfs-fuse-0.7.0-snapshot.tar.gz"

cd ..
rpmbuild -bs --clean SPECS/zfs-fuse.spec

※エラー・・・tarボールの中身がまずいらしい、強引に修正しちゃう
tar zxvf zfs-fuse-0.7.0-snapshot.tar.gz
mv official-maint-6abfdcf official
tar czvf zfs-fuse-0.7.0-snapshot.tar.gz official

※気を取り直して
cd rpmbuild
rpmbuild -bs --clean SPECS/zfs-fuse.spec

rpm -Uvh RPMS/x86_64/zfs-fuse-0.7.0-7.20120208git.el6.x86_64.rpm

よし、入った。

※20120220追記
これ結局やめました。orz
これでもってマウントオプションでxattrを有効にすると想定通りGlusterFSで使える状態でマウントできました。これでGlusterFSとZFSを組み合わせてウハウハだぜ、と思ったら、ZFS-FUSEのヘルプメッセージに書いてある通り、パフォーマンスが大幅に劣化しておりまして…。正確には覚えてなくてすみませんが、無視できない感じで猛烈に遅かったんで、結局GlusterFSもろとも諦めました。orz 今は、「2台あるから落ちても大丈夫」ではなくて「そもそも落ちない」ストレージの構築を目指して、OpenIndianaの導入に踏み切っております。なかなかうまくは行きませんね。

2012年2月7日火曜日

時々ファイルのタイムスタンプが失われてしまう件


以下のような構成があるとします。ていうか、あります。

サーバA:FreeBSD(ZFS)
サーバB:CentOS6.2(GlusterFSクライアント)
サーバC:CentOS6.2(XFS/GlusterFSホスト、ローカルでレプリケーション構成)

サーバCは複数のGlusterFSボリュームをエクスポートしており、サーバBはそれをmhddfsで束ねてマウントしています。このマウントポイントを「/hogemoge」とします。

この状態で、サーバBにて以下のようなrsyncを実行します。
rsync -a サーバA:/hoge/moge/* /hogemoge/

つまりサーバAからrsyncプロトコルで読み出し、サーバCへmhddfs+GlusterFSを介して書き出します。

すると500ファイルに1件くらいでしょうか。ファイルのタイムスタンプ(Modify)が失われる現象が発生するようです。由々しき事態です。
タイムスタンプが失われたファイルには、(ZFSが1sなのに対し)1nsの日付分解能をもつタイムスタンプが書き込まれています。ファイルを書いた時点のタイムスタンプが適用されているようです。rsyncによる日時の修正処理が適用されていない状態になっているようです。

原因は不明ですが、何らかの理由でタイムスタンプが正しく設定されていないタイミングで「ファイル情報に不一致があった場合、新しい方を正とする」というGlusterFSの仕様が発動して不要なレプリケーションが走ってしまい、不正なタイムスタンプが設定されてしまっているのかなぁと想像したりしています。rsyncがファイルを書き込んでタイムスタンプを修正した後、GlusterFSが片方のファイルのタイムスタンプを更新した段階でファイルの復元処理が走り、新しいタイムスタンプのファイルで上書きされてしまったりしているんじゃないかなぁと。

ローカルで2台レプリケーションっちゅー構成がイレギュラーなのかな。。うーん。。。
やっぱり本物のハードウェアでテストしたいなぁ。。

2012年2月4日土曜日

GlusterFS+mhddfsの書き込み速度の検証


mhddfs→GlusterFSと、FUSEを2回通ることによるパフォーマンスダウンが懸念されますので、簡単にベンチしてみました。

■別マシンの仮想サーバからネットワーク越しに書き込みテスト
○準備運動
[root@gluster01 storage]# dd if=/dev/zero of=/dev/null bs=1M count=1000
1000+0 records in
1000+0 records out
1048576000 bytes (1.0 GB) copied, 0.153937 s, 6.8 GB/s

○mhddfsを通さずGlusterFSに直接書き込み
[root@gluster01 storage]# dd if=/dev/zero of=pair01/zero3 bs=1M count=1000
1000+0 records in
1000+0 records out
1048576000 bytes (1.0 GB) copied, 22.86 s, 45.9 MB/s

○mhddfsを通してGlusterFSに書き込み
[root@gluster01 storage]# ls
pair01  pair02  pair03  pair04  storage01
[root@gluster01 storage]# dd if=/dev/zero of=storage01/zero4 bs=1M count=1000
1000+0 records in
1000+0 records out
1048576000 bytes (1.0 GB) copied, 67.8727 s, 15.4 MB/s

■同一マシンのローカルで書き込みテスト
○準備運動
[root@kfs02 storage]# dd if=/dev/zero of=/dev/null bs=1M count=1000
1000+0 records in
1000+0 records out
1048576000 bytes (1.0 GB) copied, 0.0946512 s, 11.1 GB/s

○mhddfsを通さずGlusterFSに直接書き込み
[root@kfs02 storage]# dd if=/dev/zero of=pair01/zero bs=1M count=1000
1000+0 records in
1000+0 records out
1048576000 bytes (1.0 GB) copied, 47.8904 s, 21.9 MB/s

○mhddfsを通してGlusterFSに書き込み
[root@kfs02 storage]# dd if=/dev/zero of=mhddfs01/zero2 bs=1M count=1000
1000+0 records in
1000+0 records out
1048576000 bytes (1.0 GB) copied, 45.7485 s, 22.9 MB/s

■結果まとめ
リモートとローカル、mhddfsの使用と不使用で、結果が全然違う。
ローカルの方が当然速いだろうと思いきや、リモートでmhddfs不使用がダントツで速い。でもmhddfsを通すと一気に遅くなる。
ローカルではmhddfsの使用不使用は関係なく、同じくらい遅い。
何度か繰り返しましたが、同等の結果が得られました。

■考察
意外な結果になりました。
リモートでmhddfsが遅くなるのは、ネットワークを経由することによるレイテンシ起因とかでしょうか。同期書き込みなら妥当な挙動かなぁ。
ローカルアクセスとリモートアクセスでGlusterFSの速度が段違いなのは、リモートでアクセスするとGlusterFSの処理がリモートとローカルに分割されるので高速化するとか、そんなところでしょうか。

○リモート/クライアント側
・mhddfsあり
  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
15763 root      20   0  251m  20m 1424 S 106.7  6.6  66:46.59 glusterfs
15988 root      20   0  309m  960  384 S 19.9  0.3   0:39.16 mhddfs
18092 root      20   0  103m 1696  576 S  4.0  0.5   0:01.16 dd

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND          
15763 root      20   0  251m  20m 1424 S 24.3  6.6  67:42.98 glusterfs        
18134 root      20   0  103m 1700  580 S  4.3  0.5   0:00.68 dd              

○リモート/サーバ側
・mhddfsあり
    PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 4654 root      20   0  410m  22m 1912 S 22.2  0.3  50:14.01 glusterfsd
 4658 root      20   0  410m  22m 1916 S 20.6  0.3  49:10.81 glusterfsd

・mhddfsなし
  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 4654 root      20   0  412m  22m 1912 R 34.5  0.3  50:51.63 glusterfsd
 4658 root      20   0  412m  22m 1916 R 33.5  0.3  49:46.95 glusterfsd

○ローカル(ちなみにsystem負荷が高かったので、XFSの負荷も無視できないかも知れません)
・mhddfsあり
  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
12848 root      20   0  230m  35m 2140 R 121.2  0.5   3:25.39 glusterfs
 4654 root      20   0  414m  22m 1912 S 55.1  0.3  52:43.88 glusterfsd
 4658 root      20   0  414m  22m 1916 S 50.8  0.3  51:36.55 glusterfsd
13092 root      20   0  309m  904  544 S 16.6  0.0   0:09.56 mhddfs
14460 root      20   0  103m 1688  568 S  5.6  0.0   0:00.50 dd

・mhddfsなし
  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
12848 root      20   0  230m  35m 2116 R 103.8  0.5   2:36.40 glusterfs
 4658 root      20   0  414m  22m 1916 R 100.8  0.3  50:56.41 glusterfsd
 4654 root      20   0  414m  22m 1912 R 93.7  0.3  52:05.21 glusterfsd
14452 root      20   0  103m 1684  568 S 44.4  0.0   0:02.82 dd

■で、どうする?
遅いけど、まぁ、仕方ないよね。。データの初期コピーの最初だけ、mhddfsを通さずに実施したいと思います。
読み出しは、どの組み合わせでも70〜80MB/sくらい出るようなので問題視しないことにします。。

mhddfsが導入できないクライアントのための中継機能は、ストレージサーバ自体に持たせようと思います。mhddfsで束ねた領域をNFSでエクスポートしちゃうの。パフォーマンスが多少いいので。

mhddfsとGlusterFSの組み合わせ方


GlusterFSとmhddfsの組み合わせ方を逆にしてみようと思いました。
神が舞い降りてきた感じがします。

■これまでの発想
ハードディスクをmhddfsで束ねて作った領域を、他サーバとGlusterFSでミラーリングする。
クライアントでGlusterFSをマウントする。

■これからの発想
ハードディスクをGlusterFSで他サーバのハードディスクとミラーリングする。
クライアントでGlusterFSをマウントし、それをmhddfsで束ねる。

■絵にしてみた

■違い
これまでは、サーバ内で複数のHDDをmhddfsで束ねたうえで、そこにフォルダを掘って、GlusterFSでサーバ間ミラーしようと思っておりました。
これからは、ディスク単体についてGlusterFSでサーバ間ミラーしたうえで、クライアント側でmhddfsを使って領域を結合しようと思いました。

"これまでの構成"だとディスクの容量がバラバラでも余すところなく使えますが、反面、どのディスクに何のファイルが入っているか明確になりません。たとえ容量が揃っていても、ディスクが欠落するとGusterFSが他のディスクに復元を始めてしまいます。復元を完了させるにはディスクの全域にわたってfind&statしなければならず、容量に比例して所要時間が膨大なものになります。両サーバのHDDが1台づつ故障した時点でデータロストが発生します。また、GlusterFSがmhddfsを経由してディスクにアクセスするなんてことはGlusterFS開発陣は想定していないでしょうから、どのような不具合が出るか、わかりません。

"これからの構成"だと、対になるディスクは容量が揃っていないとGlusterFSの動作に不具合が生じますが、ディスクのペアごとに、記録内容が完全に一致します。あるディスクが停止しても、GlusterFSはHAを維持してくれますが、勝手に復元することはありません。復元を完了させるには当該ペアだけfind&statすればよく、範囲を限定できます。ペアのディスクが同時に故障した時点でデータロストが発生しますが、これまでの構成よりは確率的に低いといえるでしょう。GlusterFSへの書き込みはmhddfsを使用しますが、GlusterFS自体は検証済みのファイルシステムを直接使用しますから、問題が発生する可能性は少ないでしょう。

"これからの構成"の欠点としては、全クライアントにmhddfsを導入しなければならないので、OS環境を選びます。現在主力のCentOS5.7では正しく使用できません。まぁこれは、SPOFを受認しなければなりませんが、GlusterFS+mhddfsをNFSで再エクスポートする中継サーバを用意すれば回避できそうに思います。あとディスク構成を変更した場合にクライアント間で設定変更のタイムラグが発生しますので、運用手順をよく検討すればいいかなと。

■雑感
そもそも言ってしまえば、GlusterFSのバランシングアルゴリズムが微妙なんですよねぇ。性能差を考慮しない均等なハッシュ割り当てって、ちょっとねぇ。それによる諸問題をmhddfsで回避して、実用的にしてみようという試みです。

こんな感じで、あと一晩考えてみたいと思います。

2012年2月3日金曜日

CentOS6.2にいろんなファイルシステムを導入する方法 まとめ


Linuxでいろんなファイルシステムを試してみたいと思って、いろいろ導入してみたので、メモしておきます。

■検証環境
CentOS6.2 x86_64

■ELrepoとEPELの導入(一部のファイルシステムで使用、必要に応じて)
rpm -Uvh http://elrepo.org/elrepo-release-6-4.el6.elrepo.noarch.rpm
sed -i 's/enabled=1/enabled=0/g' /etc/yum.repos.d/elrepo.repo

rpm -Uvh http://download.fedora.redhat.com/pub/epel/6/x86_64/epel-release-6-5.noarch.rpm
sed -i 's/enabled=1/enabled=0/g' /etc/yum.repos.d/epel.repo


■XFSの導入
yum install xfsprogs

ファイルの一覧取得は速いけど、削除は遅いらしい。大容量対応と高信頼性がウリだと思うんですけど、fsckにメモリを食うとか、ハード障害に弱いとか、ジャーナリングしてるくせに電プチすると結構よく壊れるとか、色々言われている様子。現時点でどこまで改善されているのか、よくわかりません。
x86_64ではローカルアクセス以外の、NFSなんかで使うとkernel panic連発とか。本当かな。でも僕もmhddfsとGlusterFSを経由してファイルを大量コピーしてる最中に原因不明のリブートが発生した時は、XFSを使っていた。その後はリブートしてない。濡れ衣かも知れないけど、状況的には疑わしいと言えるかも知れない。要重点検証。
GlusterFSではXFSが推奨されています。他のファイルシステムでも使えるけど、あんまり検証してないよって。でも僕がやろうとしているmhddfsをかましてGlusterFSで束ねるって方法だとGlusterFS開発陣による検証とか関係ないような気がするので、他のファイルシステムと横並びで比較してみたいと思います。


■JFSの導入
yum --enablerepo=elrepo install kmod-jfs

XFSと似たような特性らしいが、CPU負荷が低いらしい。悪い噂を聞かないのは、導入事例が少ないせい? ざっくり使った所、なんとなくレスポンスが遅い感じがする。ライトキャッシュの効きが甘い? 古いんでSMPではスケールしない? よくわかんないんで、ちゃんと検証しておきたいなと思います。


■ReiserFSの導入
yum --enablerepo=elrepo install kmod-reiserfs reiserfs-utils

将来性が云々と言われます。。しかも、これも性能的にはXFSとそんなに変わらないらしいけど。。まぁ何を選んでも数年後には見直すことになるでしょうから、今回は検討の対象に入れておきます。

※2012/2/4追記
GlusterFS3.2.5ではReiserFS(3.6)は使用不可のようでした。ドキュメントにはReiserFSもOKと書いてあるんですけど、正常にマウントできません。ReiserFSパーティションにattrコマンドを発行するとattrコマンドが落ちるので、多分attrコマンドかReiserFSのattr周りのハンドリングに何らかの問題があるんじゃないかと思いました。
ということは、GlusterFS on Linuxで使えるファイルシステムは、通常であればext3/4、XFS、JFSに絞られるかと思います。

■NILFS2の導入
rpm -Uvh http://www.nilfs.org/pub/centos/6/RPMS/x86_64/kmod-nilfs2-0.3.1-1.el6.x86_64.rpm
rpm -Uvh http://www.nilfs.org/pub/centos/6/RPMS/x86_64/nilfs-utils-2.1.1-6.x86_64.rpm

常時自動スナップショット。CoW。ファイル削除が鬼のように速いらしい。ただしファイルを部分的に更新すると激しく断片化するという。倉庫には向いてそうだが、起動ディスクや仮想マシンイメージには向いてなさそう。用途が読み切れないので今回は検証しません。導入方法のメモだけ残しておきます。


■Btrfsの導入
yum install btrfs-progs

ZFS並みに色々詰め込まれたファイルシステム。これが入れられれば悩み事は大体解決しそう。でもまだプロダクション環境で使える品質でないようなので検討しません。導入方法のメモだけ残しておきます。


■ZFS-FUSEの導入
yum --enablerepo=epel install zfs-fuse

もしやと思ったけれども、やっぱりattrは使えなかった。ZVOLも未サポート。よってGlusterFSとは組み合わせ不可になりますので検証しませんが、導入方法のメモだけ残しておきます。ZFS自体の性質上、パフォーマンスは半ばどうでもいい気がするんですが、FUSEでメモリ消費とかどうなんでしょうね。

GlusterFSの異常系の検証(ディスク編)


CentOS 6.2
GlusterFS 3.2.5
mhddfs 0.1.38

仮想サーバ2ノードでreplicaして、他のノードからNative Clientでマウントしました。
今回はディスクイメージをマウントしたディレクトリで検証しました。

■まとめ
実機検証じゃないと何とも言えないですね。
あるいは実機だったとしても、どこまで実際の故障を再現できるものか。
これはもう定期バックアップを前提にぶっつけ本番投入するしかないかしら。
仮想サーバでの検証では、わかんないです。

書き込み中にdisk01が消滅した場合(rm -rf disk01.img )
→何故か書き込み続行され、イメージが削除されているにも関わらず正常動作が続く。不気味。
※物理マシンでHDDをぶっこ抜いた場合とは挙動が違うんじゃないかな。。

書き込み中にmhddfsがumountされた場合(umount -l mhddfs01)
→片方のノードのみで書き込み続行。
→書き込みが完了するとアクセスできなくなる。
→復帰すると稼動再開

書き込み中にdisk01がumountされた場合(umount -l disk01)
→片方のノードのみで書き込み続行。
→書き込みが完了するとアクセスできなくなる。
→復帰すると稼動再開


■準備
mkdir /storage
cd /storage
dd if=/dev/zero of=disk01.img bs=1M seek=1000 count=1
mkfs.xfs disk01.img
dd if=/dev/zero of=disk02.img bs=1M seek=1000 count=1
mkfs.xfs disk02.img
mkdir disk02
mkdir disk01
mount -o loop disk01.img disk01
mount -o loop disk02.img disk02
mkdir mhddfs01
mhddfs -o mlimit=10% disk01,disk02 mhddfs01

↑これを192.168.0.131と192.168.0.132の両方でやっておく

gluster peer probe 192.168.0.132
gluster volume create kfs replica 2 192.168.0.131:/storage/mhddfs01/kfs 192.168.0.132:/storage/mhddfs01/kfs
gluster volume start kfs

他のサーバでマウントする。
mkdir kfs
mount -t glusterfs 192.168.0.131:kfs kfs

■書き込み中にdisk01が消滅した場合(rm -rf disk01.img )
ディスクが物理的に外れた場合を想定。

書き込み中に192.168.0.132でdisk01.imgをrm -rfする
→何も起こらない。書き込み続行。

192.168.0.132のdisk01.imgは確かに消滅しているのに、mhddfsやdisk01の中身は正常に書き込み進行しているように見える。
GlusterFSやmhddfsを通さずとも、mountもdf -khもstatも、あまつさえdd of=/dev/nullでさえも、あたかもそこに完全なファイルが正常に存在しているかのような挙動を示す。
一体どこに書き込んだのか不明である。disk01を一旦umountすると、復旧できない(だってdisk01.imgがないもの)
消す前のdisk01.imgを作り直したり再設置したりしても何ら影響無し。切り離されて動いている様子。
容量的には元のdisk01.imgと同じサイズが上限になる。それでも割り当てメモリは超えてるし、swapしてる様子も無い。

えー、GlusterFSもmhddfsも関係ないっぽいけど、なんか驚愕の挙動なんですけど。。
仮想マシンイメージの実サイズが膨らんでたので、どこかに一時ファイルが作られているとしか。。。
ディスクイメージをマウントするのに-o loopしてるので、その影響でしょうか。。
実機じゃないと検証にならんな。。でも空きマシンは無いなぁ。。

■書き込み中にmhddfsがumountされた場合(umount -l mhddfs01)
mhddfsが落ちた場合を想定。内容が消失してローカルディレクトリに差し変わる結果、brickとして使っているディレクトリが消滅する。
GlusterFSがどのように動作するか、見物である。

書き込み中に192.168.0.132でumount -l mhddfs01する
→何も起こらない。書き込み続行。

書き込み完了後、クライアントでマウントポイントにアクセスできなくなる。
> ls: kfs: 入力/出力エラーです

mhddfs01を再マウントすると自動復帰する

■書き込み中にdisk01がumountされた場合(umount -l disk01)
mhddfsが束ねているファイルシステムが落ちた場合を想定。
mhddfsがどのように動作し、GlusterFSにどのような影響を与えるか、見物である。

書き込み中に192.168.0.132でdisk01をumount -fする
→192.168.0.132は止まり、192.168.0.131だけで書き込み続行。

書き込み完了後、クライアントでマウントポイントにアクセスできなくなる。
> ls: kfs: 入力/出力エラーです

disk01.imgを再マウントすると自動復帰する

2012年2月2日木曜日

GlusterFSの異常系の検証(ディレクトリ編)


CentOS 5.7
GlusterFS 3.2.5
※今回はmhddfsは無し。

2ノードでreplicaして、他のノードからNative Clientでマウントしました。
今回はディレクトリで検証しました。

こんな感じ。
gluster volume create kfs replica2 192.168.0.252:/storage/disk01/kfs 192.168.0.252:/storage/disk02/kfs

■まとめ
異常があったら、まずglusterdを落として、じっくり検討。
復帰操作は一発勝負。勝手に書かれちゃうんで。

glusterdプロセスが落ちた場合
→いずれかのプロセスが存在すれば正常稼動する。
→両方落ちると書き込み異常終了。
→いずれかが復帰すると稼動再開。
※これは想定内な感じがします。

brickのディレクトリが消滅した場合
→いずれかのノードが稼動していれば正常稼動する。
→両方落ちても書き込み続行。データは行方不明に。
→復帰時は両方のノードが稼動するまで復帰できない
※要するに考慮されてないっぽいので、こういうオペレーションはダメっぽい


■glusterdプロセスが落ちた場合の挙動確認
書き込み中に片方のglusterdを落とす
→残ったノードだけで書き込み続行

書き込み中に落としたglusterdを、その書き込み中に上げる
→すぐには何も起こらないが、書き込み終了後に復元動作が走り、落とした方が修復される

書き込み中に両方のglusterdを落とす
→書き込み異常終了
> dd: writing `kfs/zero': 通信端点が接続されていません
> dd: 出力ファイル `kfs/zero' をクローズ: 通信端点が接続されていません
マウントは外れていないが、参照できない状態になっている。
変なファイルがローカルディスクに書き込まれる恐れは無さそう。

そのまま片方のglusterdを上げる
→即、自動復帰する

もう片方のglusterdも上げる
→タイムラグがあるようだが、数十秒以内に自動復帰する
→ファイルアクセスをキックに、差分も自動修正される

■brickのディレクトリが消滅した場合の挙動確認
書き込み中に片方のディレクトリをrm -rfする
→残ったノードだけで書き込み続行

書き込み中に消したディレクトリを、その書き込み中に再作成する
→復帰するようだが、直ちには何も起こらない
→ファイルアクセスをキックに、差分も自動修正される

書き込み中に両方のディレクトリをrm -rfする
→何も起こらない。書き込み止まらない。
マウントは外れていないが、参照できない状態になっている。
変なファイルがローカルディスクに書き込まれる恐れは無さそうだが、データはどこに消えているか不明。

そのまま片方のディレクトリを再作成する
→自動復帰しない。
> ls: kfs: 入力/出力エラーです
> touch: cannot touch `kfs/test': そのようなファイルやディレクトリはありません
> stat: cannot stat `kfs': そのようなファイルやディレクトリはありません

サーバ側のglusterdを再起動する
→何も起こらない。変化無し。
> ls: kfs: 入力/出力エラーです
> touch: cannot touch `kfs/test': そのようなファイルやディレクトリはありません
> stat: cannot stat `kfs': そのようなファイルやディレクトリはありません

クライアント側のglusterdを再起動する
→何も起こらない。変化無し。
> ls: kfs: 入力/出力エラーです
> touch: cannot touch `kfs/test': そのようなファイルやディレクトリはありません
> stat: cannot stat `kfs': そのようなファイルやディレクトリはありません

クライアントで一旦umountし、再マウントする
→何も起こらない。変化無し。
> ls: kfs: 入力/出力エラーです
> touch: cannot touch `kfs/test': そのようなファイルやディレクトリはありません
> stat: cannot stat `kfs': そのようなファイルやディレクトリはありません

もう片方のディレクトリも再作成する
→自動復帰する

同一ディレクトリに大量のファイルを設置した場合のパフォーマンス調査


ext3で、同一ディレクトリにファイルが大量に存在すると遅くなるというのは有名かと。
ext4では、H-treeで高速化したとwikipediaに書いてあったんですけど、期待ほどではなかった様子。。

以下、4985個のファイルが置いてあるディレクトリで試しました。一応2回づつ。
(ユーザーとしての個人的感覚では、そんなに大量というほどのファイル数ではないつもり)

■ext4(@CentOS6.2)
time ls disk01/kfs/pb/generated/122
real    0m16.153s
user    0m0.044s
sys     0m0.288s

time ls disk01/kfs/pb/generated/122
real    0m8.402s
user    0m0.034s
sys     0m0.104s

■XFS(@CentOS6.2)
time ls xfs05/kfs/pb/generated/122
real    0m0.175s
user    0m0.030s
sys     0m0.142s

time ls xfs05/kfs/pb/generated/122
real    0m0.075s
user    0m0.023s
sys     0m0.047s

■JFS(@CentOS6.2+ELrepo)
yum install jfsutils
rpm -Uvh http://elrepo.org/elrepo-release-6-3.el6.elrepo.noarch.rpm
yum install enablerepo=elrepo kmod-jfs
mount /dev/sdg jfs07
mount /dev/sdh jfs08

time ls jfs07/kfs/pb/generated/122
real    0m0.134s
user    0m0.021s
sys     0m0.092s

time ls jfs07/kfs/pb/generated/122
real    0m0.073s
user    0m0.026s
sys     0m0.043s

■ZFS(@FreeBSD)
/usr/bin/time -h -p ls /kfs01/kfs/pb/generated/122
real 0.12
user 0.00
sys 0.01

/usr/bin/time -h -p ls /kfs01/kfs/pb/generated/122
real 0.16
user 0.00
sys 0.01

ext4の圧倒的遅さが際立っております。
これで問題にならない使い方なら問題ないんでしょうけれども、僕の場合は問題ありありなので困ります。デジタル写真とか扱ってたら1フォルダ数百〜数千個くらいはすぐ貯まっちゃうでしょう。。貯めるなってのはユーザーフレンドリーじゃないのでダメです。

では何がいいかと言うと。。んー。。。やっぱりJFSかぁ?

mhddfsの容量フル時の挙動について(不具合報告)

mhddfsの不具合? マニュアルの記載ミス? を見つけました。
けどロシア語よくわかんないしw こちらで。

■対象バージョン
最新:mhddfs_0.1.38.tar.gz

■内容
# df -kh
Filesystem            Size  Used Avail Use% マウント位置
/dev/sda              917G  867G  4.0G 100% /storage/disk01
/dev/sdb              917G  3.1G  868G   1% /storage/disk02
/storage/disk01;/storage/disk02
                      1.8T  870G  872G  50% /storage/mhddfs01

うーむむむ、1台目を100%まで使い切っちゃってるじゃないか。空き容量が一切ないのはメンテナンス時にハマる可能性があるような気がする。READMEにはDefault value is 25%って書いてあったんだけどな。。
http://svn.uvw.ru/mhddfs/trunk/README

ていうかソース読んでみた。嘘じゃん。バグってるじゃん。
http://svn.uvw.ru/mhddfs/trunk/src/parse_options.c

#define DEFAULT_MLIMIT ( 4l * 1024 * 1024 * 1024 )
#define MINIMUM_MLIMIT ( 50l * 1024 * 1024 )

どこを読んでもデフォルト25%なんてインテリジェントなオプション解釈はやってない感じがする。ていうかマウント時のメッセージにもはっきり出てるじゃん orz
mhddfs: move size limit 4294967296 bytes

4GB決め打ちですね。容量9割がた埋まるとパフォーマンスが激落ちする某FSなんかで使うとハマるでしょうねぇ。。

%指定を解釈するロジックは入っていたので(100未満の値は%と見なしてますね)、その点は問題ないようです。

mhddfsを使う時は、今のところ、-o mlimit=XXの明示が必須ということで。

2012年2月1日水曜日

mhddfs+GlusterFSでレプリケーションを復元してみる


相変わらずローカル環境でのテストを続けております。
今回は、GlusterFSによるレプリケーションの自動修復を試してみました。

■準備
書き込み稼動中のディスク1台分のマウントを外してみた(umount -f)
→mhddfsおよびGlusterFSでinput/output errorが出て書き込めなくなった。今回はサーバ一台だけでテストしてますが、これ、複数サーバの1つが落ちた状態でも同じ動作なら意味ないですね。全然HAじゃない。要検証。

外したディスクをフォーマットした(mkfs.ext4 /dev/sda)
→あえてXFSじゃないのは、ext4も試してみたかったから。安定するならどっちでもいい。

ext4をmountした(mount)
→mhddfsもGlusterFSも、自動復帰しなかった。

mhddfsをrestartした(umount&mount)
→GlusterFSは自動復帰しなかった。

GlusterFSをrestartしてクライアントでマウントし直した(service gusterd restart, umount & mount)
→アクセスおk

■自動修復させてみた
ファイルにアクセスする度に自動復元されますが、案の定と言いますか、想像並みに重い感じでした。

ディレクトリの内容をlsすると、そのディレクトリ内のファイルが復元され、lsが帰ってきます。この段階ではファイルが欠損しているほうのディスクに空ファイルが作られるのみで、(おそらくそこにはattrで色々書かれていて)データ本体はまだミラーされていない状態に留まります。データ本体は同期しないので高速なんでしょうが、3000ファイルで30秒くらいかかりました。その間lsはフリーズ状態で待機です。対象ディレクトリに子ディレクトリエントリが含まれていた場合、おそらくその子ディレクトリ内のファイルの数を確定するためでしょうが、やたらめったら時間がかかります。複数のサブディレクトリ内に合計ウン十万個のファイルがあるディレクトリをlsした時、時間は計り忘れましたが2時間近くはCtrl-Cも効かなかったかなぁ。

データ本体はデータを読み出した時に復元されます。読みながら書いていくので、所要時間もそれなりです。ただlsの場合と違ってデータ本体に一括アクセスすることは普通ないと思いますので、これはさほど問題ないかなと思いました。でも3000ファイル4GBをtime cat * > /dev/nullで6分強。全体を完全に同期完了させるには全ファイルを読み出さないとならないので、データ量が増えると所要時間が半端無いですね。まぁ未使用領域はスキャンしないので、mdadmよりはマシと思うことにしましょうか。。brickに直接アクセスして同期すべきファイルを特定するスクリプトを作って回したら、読み出す範囲を限定できるので、部分的な損傷の時に高速化できるかなぁと思った。ちなみにcatで読み出すより公式コマンド「find <gluster-mount> -print0 | xargs --null stat >/dev/null」のほうが倍くらい速かった。20〜30MB/secくらい。バックアップマシンからrsyncでリストアするよりもちょびっと速いかも知れない。

■まとめ
当然ながらGlusterFSでのミラー復元はデータ量に応じて膨大な時間を要するので、これをアテにしたHDDの取り付け取り外しは現実的じゃないなと思いました。とはいえ普通のRAIDだって復元には膨大な時間がかかるわけですので、純粋に筐体間RAIDと考えて非常事態に備えるHA目的での運用は、あり得るのかなと思いました。

あとは複数サーバをまたがるボリュームの、異常系の動作検証が必要ですね。書き込みエラーでも動作継続できますように。

GlusterFS+mhddfs+XFSを試してみた


mhddfsで束ねたXFS領域をGlusterFSでミラーする構成を試しています。

■感想
・GlusterFSはCPUを大食いだ。
・速度は問題無し。むしろ想像より速くてびびった。
・なんか不安定な感じがする。だいぶ不安。

■構成
最終的にはネットワーク越しで構成しますが、マシンに空きがないので、とりあえずローカルでテスト。

○検証方法
・CentOS6.2をSDカードにインストールしてmhddfsとGlusterFSを導入する。
・XFSでフォーマットしたSATA HDDを4台用意する。
・mhddfsで2台ずつ束ねる。
・束ねたhdd領域をGlusterFSでレプリカする。
・他のサーバでマウントし、適当なテストデータを大量コピー(rsync)して様子を見る。

○挙動
以下いずれもGlusterFSかmhddfsかXFSか、どの影響かわかりませんが、気が付いた点。

・パフォーマンスは悪くない。Write/Read共に80〜90MB/sくらい出てます(WD10EADS-00L5B1)。なんだ、ZFSなんかよりよっぽど速いじゃないか。RAID1の同一ディスクにイメージファイル作ってXFSフォーマットしてマウントしてGlusterFSでレプリカして、なんていう構成だと4MB/sくらいだったし、mhddfsは遅いという話を散見してたのでドキドキしてましたが、これなら実用に堪える感じ。同一ディレクトリに1万個くらいファイル設置してみましたが、これも応答速度に問題無し。XFSのおかげでしょうが。ランダムアクセスはあんまり発生しないので調べなくていいや。

・コピー途中に本構成を仕込んでるサーバが突然リブートした模様; SDカード起動の影響かな。。これなんか不安定なんすよ。SDカードはそのままにアダプタを変えると挙動が大幅に変わるし。でも起動ディスクごときにSATAポート取られたくないんだよなぁ。。

・発生条件がよくわかりませんが、permission errorでmhddfs領域に書き込めなくなる場合がある模様(もちろんpermission設定的には書き込める状態ですよ、ディレクトリ777とかで)。mhddfsのマウントし直しで解消。書き込めない最中はクライアントに対してはGlusterFSを通じてpermission errorがそのまま返る模様。多いに不安。dmesgには何も出てないし発生の瞬間には立ち会っていなかったのでよくわかりませんが、両方とも発生してたので、もしかするとmhddfsのせいじゃないかも知れない。またネットワーク越しだと違うかも知れない。

・mhddfsをGlusterFSで束ねるんですが、GlusterFS領域をローカルでマウントできません。mountコマンドを打っても無視されます。もちろんmhddfsを通さなければ動作します。FUSEの2重化が不可なんでしょうかね。ネットワーク越しではマウントできるので目をつぶることにします。

・ディスクへの書き込みは同時ではなく数秒おきにラウンドロビンするようですが、同時並行アクセスが全くないわけではないようです。ライトキャッシュも効いてる様子で、細かいファイルはかなり溜め込んでから書き込んでます。メモリ消費具合や挙動から察するに、GlusterFSがラウンドロビンして、XFSが遅延書き込みしてる感じかな?(想像) ということはbrick間やディスク間では結構な量の不一致が発生してるはずなので、運用上は留意が必要かなぁ(想像)。混ぜるな危険、みたいな。もしレプリカ修復が日常的に発生するようだと、 レプリカ3本欲しいかも。。でもコストが。。

・CPU負荷が高いす。マルチコアプロセッサは必須かなぁ。atomでは無理そうだ。具体的にはこんな感じ↓@Core 2 Quad Q6600

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 2278 root      20   0  737m  28m 2012 S 68.1  0.4  62:26.57 glusterfsd
 2282 root      20   0  737m  24m 2012 R 66.4  0.3  61:48.47 glusterfsd
 5631 root      20   0 1294m  16m  608 S 21.3  0.2  20:00.89 mhddfs
 5648 root      20   0  975m  22m  604 S 20.3  0.3  19:15.53 mhddfs

書き込みアクセス中。mhddfs領域2つをローカルでレプリケーションしてるので2つずつ上がってると思われる。
読み込みは、glusterfsdプロセスが増える場合と増えない場合がある模様。条件は不明。
メモリ消費量が小さくないようなので、小メモリ構成だと同時アクセスが増えたら危険かも知れない。この感じだと、同時100アクセスに備えるなら4GB+キャッシュ分は必要でしょうかねぇ。

もうちょっとテストしてみます。