disk syncの挙動を細かく制御する

mmap I/Oアプリケーションにおけるdisc sync

ファイルを読み書きするようなアプリケーションにおいて、キャッシュの管理をOS任せにしても良いのならば、ファイル全体をmmapしてしまうと非常に楽にI/O処理を書くことができる。

しかし、mmapしてデータを書き込むだけで、即座にディスクにデータが書き込まれるわけではない。
明示的にmsync(MS_SYNC)を発行して同期を取る必要がある。

ここまでは一般的な話。

さて、0x1000-0x2000, 0x8000-0x10000といった複数の領域を同期する為にはどうするか。ナイーブな実装としては、msync(MS_SYNC)を2回に分けて発行する方法がある。

msync(0x1000, 4096, MS_SYNC);
msync(0x8000, 8192, MS_SYNC);

しかし、これでは最初の領域を確実に書き込んだ後、改めて次の領域を書き込む処理を発行するため、効率がわるい。

これら2つをいっぺんに発行するにはどうするか。MS_ASYNCでdirtyだと明示しておいて、あとでまとめてfsyncで書き込んでやれば良い。というかMS_ASYNCフラグの存在意義が長らく謎だったんだけど、こういう具合につかうのね。

msync(0x1000, 4096, MS_ASYNC);
msync(0x8000, 8192, MS_ASYNC);
fsync(fd);

さらに補足すると、Linux 2.6.17以降ではmsync(MS_ASYNC)は何もしないため、これらのmsyncの発行自体不要で、fsyncだけすれば良いらしい。
cf. http://lxr.free-electrons.com/source/mm/msync.c

fsync(fd);

MS_ASYNCなんてやっぱり要らなかったんや...!

でもOSXとかSolarisとか他の実装はみてないので、それらではちゃんとやってあげたほうがいいかとおもわれます。

ファイルのメタデータの更新を抑えて高速化する

DBみたいにアホみたいにfsyncを発行するアプリケーションを書いていると、fsyncがファイルのメタデータを更新するオーバーヘッドが気になり始める。

fsync(2)が行うことは実は2つある。1つはwrite/msync等が発行されながら、実際に書き込まれていなかったデータをディスクにフラッシュするという動作で、おそらく皆さん意識することだろうと思う。もう1つはファイルのメタデータの更新動作で、ファイルの更新時刻(mtime)やファイルサイズのエントリを更新する。

このメタデータの更新動作は、ファイルサイズの変更を伴わない、データの上書きを行う場合では不要になる。

その場合、fsync(2)の代わりにfdatasync(2)を行うことで、メタデータの更新を避けることができる。
fdatasyncはtime系以外の更新がない場合、メタデータの更新を省略する。
cf. http://linux.die.net/man/2/fdatasync

同じことは、範囲指定版fdatasync(2)であるsync_file_range(2)を使っても可能。ただこちらはメタデータの更新を絶対に行わない?(指摘より)
http://linux.die.net/man/2/sync_file_range

また、上記のようにmmap I/Oを行っている場合でも有効で、msync(MS_SYNC)はメタデータの更新を伴うが、msync(MS_ASYNC); fdatasync(fd)と呼び出すことでメタデータの更新を省略することができる。

ただし、以上の話はデータの上書きにのみ適用できることに注意。
新規データの書き出しには、データを書き込むブロックの確保というメタデータの操作が発生するため、fsync(2)を使わないと安全ではない。

あと、linux以外の挙動に関してはよく知らない。