Linuxのローカルファイルシステムの話です。かなりハマっていたので記事として残しておきます。
既存ファイルがある状態でO_TRUNCを指定してopen(2)を呼び出し、適当に書き込んだあとclose(2)すると、非同期ながらWritebackされ物理IOが発生します。close時はfsyncせず、書き込まれたデータはDirty状態でページキャッシュに保存されるだけのはずなのだが(参考:Linux の close は fsync 相当を調べる – naoyaのはてなダイアリー)。。。何を言っているのかわからねーと思うので、状況を再現する手順は以下のとおり。
- ファイルを適当に作っておく。
$ echo test > test.dat
- 以下のようなソースファイルを作成し、コンパイルしておく
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(void) { int fd; char *p = malloc(10*1024*1024); fd = open("test.dat", O_TRUNC | O_CREAT | O_WRONLY, 0666 ); write(fd, p, 10*1024*1024); close(fd); return 0; }
- 実行する前に、/proc/meminfo の Dirty・Writeback の値を確認する。
- 実行する(ディスクランプ・iostatでディスクへの書き込みを確認する)
- 実行後、/proc/meminfo の Dirty・Writeback の値を10秒間程度確認し、実行前に比べDirtyの値が10M増えたままであればsyncされていないが、Dirtyの値が変わらない、もしくは、Dirtyの値が増えた後減って実行前の状態に戻った(そのときwritebackの値が0より大きい値)、ようであれば writeback されていることになる。
※ vm_dirty*** 系のパラメタはこのタイミングでwritebackされないように適当に大きくしておく。
色々試してみたところファイルシステム依存で、ext3(data=writeback), xfs についてはclose(2)時にディスクへの書き込みが行なわれた。ext3(data=ordered), ext2, reiserfsについてはそのようにはならなかった。また、ext3(data=writeback), xfs でも既存ファイルが無い状態での O_TRUNC ではWritebackされず、Dirtyのまま残る。
ファイルシステム毎に試してみたところ以下のようになった。(たぶん合っていると思いますが、間違っていたら指摘ください)
ファイルシステム | 既存ファイル無しでO_TRUNC | 既存ファイル有りでO_TRUNC |
---|---|---|
ext2 | close(2)のタイミングでデータはwritebackされず、Dirtyのままである | 同左 |
ext3 (data=ordered) |
close(2)のタイミングでデータはwritebackされず、Dirtyのままであり、メタデータ・データはcommit間隔にて同期される。 | close(2)のタイミングでデータはwritebackされず、Dirtyのままであり、メタデータ・データはcommit間隔にて同期される。(しかし、同期された後もDirtyからは消えない?) |
ext3 (data=writeback) |
メタデータはcommit間隔にて同期される。close(2)のタイミングでデータはwritebackされず、Dirtyのままとなる。 | close(2)時にwritebackされる(close(2)はsyncの復帰を待たず復帰) |
reiserfs | close(2)のタイミングでデータはwritebackされず、Dirtyのままである | 同左 |
xfs | close(2)のタイミングでデータはwritebackされず、Dirtyのままとなる。 | close(2)時にwritebackされる(close(2)はsyncの復帰を待たず復帰) |
今まで、ファイルへの書き込みはLinuxのページキャッシュ機構により、非同期でwritebackされると思っていたがこういうタイミングでもwritebackされるとは思っていなかった。お仕事でLinuxを使う場合は、close(2)のターンアラウンドタイムとディスクアクセスが性能見積りと異なる場合があるので要注意かなぁ。(こういう仕組みになっているのは、どうやらジャーナルとデータとの関係のようだが詳細が分かったらまた追記します)どうやらtruncateしてファイルサイズが0でファイルの実体がsyncされていない状態でクラッシュすると、ファイルが失われるため、それを防止するためにtruncate時は早めにsyncするようです。
参考までにSDカードにファイルシステムを作ってみて、上記プログラムをstrace上で動かしてみたときのシステムコールの時間を残しておきます。
既存ファイル有りでO_TRUNC:(単位はms)
ext2 | ext3(data=writeback) | ext3(data=ordered) | reiserfs | xfs | |
---|---|---|---|---|---|
open | 0.065 | 0.0925 | 0.086 | 0.134 | 0.184 |
write | 53.775 | 90.2 | 227.090 | 65.586 | 37.911 |
close | 0.027 | 17.874 | 0.026 | 0.047 | 8.159 |
(太字にした)ext3, xfs のclose時にwritebackの処理のため(?)に時間がかかるようです。また既存ファイルが無い状態での同じプログラムを動かした結果は以下のようになり、close時のwritebackが行なわれませんでした。
既存ファイル無しでO_TRUNC:(単位はms)
ext2 | ext3(data=writeback) | ext3(data=ordered) | reiserfs | xfs | |
---|---|---|---|---|---|
open | 0.0685 | 0.075 | 0.074 | 0.1 | 0.134 |
write | 55.501 | 93.967 | 134.721 | 67.165 | 37.093 |
close | 0.0255 | 0.028 | 0.024 | 0.046 | 0.0815 |
2011/1/4 追記:
ext3 のマウントモードがwritebackである旨を追加し、ordered の場合の動作追記。