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台レプリケーションっちゅー構成がイレギュラーなのかな。。うーん。。。
やっぱり本物のハードウェアでテストしたいなぁ。。