2006年04月13日
NFS経由で正しい内容が読めない場合がある問題の原因と解決策
今日はLinuxのNFSの問題について書きたいと思います。
(Linux以外の実装は調べていませんm(_ _)m)
その問題とは、NFS経由で読んだファイルの内容が正しくない場合がある、というもので、NFSクライアントがnoacオプションつきでmountしていても発生してしまいます。(noacオプションが指定されていない場合は、これから述べる原因とは関係なく、メタ情報がキャッシュされるのでそもそも正しい情報が得られない可能性があります)
(Linux以外の実装は調べていませんm(_ _)m)
その問題とは、NFS経由で読んだファイルの内容が正しくない場合がある、というもので、NFSクライアントがnoacオプションつきでmountしていても発生してしまいます。(noacオプションが指定されていない場合は、これから述べる原因とは関係なく、メタ情報がキャッシュされるのでそもそも正しい情報が得られない可能性があります)
この問題が発現する条件は以下の通りです。これらを全て満たした場合のみ、問題が発生します。
このとき、自分より後に誰かが書き込んでいても、それより前に自分で書き込んだデータが読み出されてしまいます。
ちょっとわかりづらいので図示します。
ある一秒の間(この場合、00:00〜00:01)に、NFSクライアント1と2がそれぞれ図のようにreadとwriteをしました。
そして00:01の(数秒)後に、NFSクライアント1がreadすると、本来ならば時間的に最後のNFSクライアント2が書き込んだ内容("xyz")が読み出せるはずなのですが、"abc"が読み出されてしまいます。
これが発現条件です。
次に原因について書きます。
NFSクライアントのコードの、linux/fs/inode.cのnfs_refresh_inodeを見てみると、NFSクライアントはキャッシュを持っていて、ファイルのmtimeやサイズなどが同じ場合はキャッシュを返すような実装になっています。
先の条件の場合、ファイルサイズは変わりませんし、ReiserFSやEXT3の[cma]timeの分解能は「秒」であるため、mtimeは変わっていないように見えてしまいます。
したがって、NFSクライアントはキャッシュしていた"abc"を返してしまうわけです。
原因がわかったので対策を考えてみます。
というわけで、最後の「秒よりも小さい分解能を持つファイルシステムを使う」がいちばんスマートなのでこの方法で対応したいと思います。この対応法の場合、「一秒の間で」という条件がなくなるわけでなく「一ナノ秒の間で」に変わるだけなのですが、まぁ現実的には一ナノ秒で競合することはまずないのでこれでよしとします。
いくつかファイルシステムを試したところ、以下のものは秒単位でした。
試した範囲では、唯一、XFSがナノ秒のタイムスタンプをサポートしていました。
NFSサーバのファイルシステムをXFSにしたところ、数時間テストプログラムを走らせた限りでは問題ありませんでした。
めでたしめでたし。
となるはずだったのですが、全く別の問題(これは機会があれば後日にでも…)で、XFSでは問題があることが判明してしまい、調べたところReiserFSでもだめで、EXT3なら問題がないことがわかりました。
なのでEXT3をnano second対応にできないか調べたところ、LKML (Linux Kernel Mailing List)にパッチが投稿されていたのを発見しました。
早速このパッチを当ててみたのですが、相変わらず分解能は秒のままでナノ秒になりません。
パッチを読んでみると、inodeの大きさで分岐しているので、mke2fsのmanを読んでみました。が、inodeの数を制御するオプションはあっても大きさを変えるオプションは見当たりません。
そこでmke2fsのソースコードを読んでみると、-Iというアンドキュメントなオプションを見つけました。
どうやらこれでinodeの大きさを変えられそうなので、
としたところ、見事、ナノ秒のタイムスタンプになりました。
mke2fsしたときのwarningが気になりますが、数日間のストレステストを経て、今のところこれで元気に動いています。
ただひとつ注意点があって、umountしてmountしなおすと、[cma]timeが秒単位に切り詰められてしまいます。弊社でこのシステムを使っている用途ではこの点は許容できるのですが、許容できない場合もあると思うので心の隅に置いておいた方がよいと思います。
(ひ)
- 同じファイルに対して
- 一秒間の間に
- 異なるNFSクライアントホストから
- 同じサイズのデータを書き込んだ
このとき、自分より後に誰かが書き込んでいても、それより前に自分で書き込んだデータが読み出されてしまいます。
ちょっとわかりづらいので図示します。
時間 NFSクライアント1 NFSクライアント2
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
│
[00:00]…………………………………………………………………
│ read →"123"
│ write←"abc"
│
│ read →"abc"
│ write←"xyz"
[00:01]…………………………………………………………………
│ read →"abc" read →"xyz"
│ ★"xyz"が正しいはずだが
│ "abc"が読めてしまう
↓
凡例
read →"XXX" … "XXX"を読み出した
write→"XXX" … "XXX"を書き出した
ある一秒の間(この場合、00:00〜00:01)に、NFSクライアント1と2がそれぞれ図のようにreadとwriteをしました。
そして00:01の(数秒)後に、NFSクライアント1がreadすると、本来ならば時間的に最後のNFSクライアント2が書き込んだ内容("xyz")が読み出せるはずなのですが、"abc"が読み出されてしまいます。
これが発現条件です。
次に原因について書きます。
NFSクライアントのコードの、linux/fs/inode.cのnfs_refresh_inodeを見てみると、NFSクライアントはキャッシュを持っていて、ファイルのmtimeやサイズなどが同じ場合はキャッシュを返すような実装になっています。
先の条件の場合、ファイルサイズは変わりませんし、ReiserFSやEXT3の[cma]timeの分解能は「秒」であるため、mtimeは変わっていないように見えてしまいます。
したがって、NFSクライアントはキャッシュしていた"abc"を返してしまうわけです。
原因がわかったので対策を考えてみます。
- ファイルサイズを変える
- 書き込む度にダミーデータをつけるなどして、毎回ファイルサイズが異なるようにすれば、発現条件を崩せるので問題回避できます。が、この方法はあまりにもダサすぎますね。
- ファイルをrenameする
- mvなどでファイルをrename(2)すれば、inodeが変わるので恐らく回避できると思います。
- 書き込み間隔を一秒以上にする
- 排他制御をして、書き込んでから一秒待ってロックを開放すれば、発現条件を崩せ
ます。が、この方法もダサすぎです。
- 秒よりも小さい分解能を持つファイルシステムを使う
- 少なくともLinuxのNFSv3の実装では、ナノ秒単位のタイムスタンプをサポートしているので、NFSサーバのファイルシステムをナノ秒タイムスタンプをサポートしているものにすれば、「一秒の間で」という条件を崩せます。
というわけで、最後の「秒よりも小さい分解能を持つファイルシステムを使う」がいちばんスマートなのでこの方法で対応したいと思います。この対応法の場合、「一秒の間で」という条件がなくなるわけでなく「一ナノ秒の間で」に変わるだけなのですが、まぁ現実的には一ナノ秒で競合することはまずないのでこれでよしとします。
いくつかファイルシステムを試したところ、以下のものは秒単位でした。
- ext2
- ext3
- ReiserFS v3
- ReiserFS v4
- JFS
試した範囲では、唯一、XFSがナノ秒のタイムスタンプをサポートしていました。
$ df -T .|awk '{print $2}'
Type
xfs
$ :> foo
$ ls --full-time foo
-rw-r--r-- 1 klab klab 0 2006-04-13 19:50:11.284119008 +0900 foo
$ stat foo
File: `foo'
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: 9301h/37633d Inode: -1342125903 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 1328/ klab) Gid: ( 1328/ klab)
Access: 2006-04-13 19:49:50.649255984 +0900
Modify: 2006-04-13 19:50:11.284119008 +0900
Change: 2006-04-13 19:50:11.284119008 +0900
NFSサーバのファイルシステムをXFSにしたところ、数時間テストプログラムを走らせた限りでは問題ありませんでした。
めでたしめでたし。
となるはずだったのですが、全く別の問題(これは機会があれば後日にでも…)で、XFSでは問題があることが判明してしまい、調べたところReiserFSでもだめで、EXT3なら問題がないことがわかりました。
なのでEXT3をnano second対応にできないか調べたところ、LKML (Linux Kernel Mailing List)にパッチが投稿されていたのを発見しました。
- Patch: Ext3 nanosecond timestamps in big inodes
- http://marc.theaimsgroup.com/?t=110584682400001&r=1&w=1
- http://marc.theaimsgroup.com/?l=linux-kernel&m=110584685728040&w=1
早速このパッチを当ててみたのですが、相変わらず分解能は秒のままでナノ秒になりません。
パッチを読んでみると、inodeの大きさで分岐しているので、mke2fsのmanを読んでみました。が、inodeの数を制御するオプションはあっても大きさを変えるオプションは見当たりません。
そこでmke2fsのソースコードを読んでみると、-Iというアンドキュメントなオプションを見つけました。
#ifdef EXT2_DYNAMIC_REV
case 'I':
inode_size = strtoul(optarg, &tmp, 0);
if (*tmp) {
com_err(program_name, 0,
_("invalid inode size - %s"), optarg);
exit(1);
}
break;
#endif
どうやらこれでinodeの大きさを変えられそうなので、
# mke2fs -j -I 256 /dev/sdb1
: : :
Warning: 256-byte inodes not usable on most systems
: : :
としたところ、見事、ナノ秒のタイムスタンプになりました。
$ df -T . | awk '{print $2}'
Type
ext3
$ :> foo
$ ls --full-time foo
-rw-r--r-- 1 klab klab 0 2006-04-13 20:33:10.586821000 +0900 foo
mke2fsしたときのwarningが気になりますが、数日間のストレステストを経て、今のところこれで元気に動いています。
ただひとつ注意点があって、umountしてmountしなおすと、[cma]timeが秒単位に切り詰められてしまいます。弊社でこのシステムを使っている用途ではこの点は許容できるのですが、許容できない場合もあると思うので心の隅に置いておいた方がよいと思います。
(ひ)
klab_gijutsu2 at 20:42│Comments(0)│TrackBack(0)