民芸的プログラミング 〜ソフトウェア開発日記〜

アクセスカウンタ

zoom RSS ファイルを保存するプログラムコード

<<   作成日時 : 2009/03/14 08:26   >>

ブログ気持玉 0 / トラックバック 0 / コメント 0

スラッシュドット・ジャパンで見かけた記事。
http://slashdot.jp/hardware/09/03/13/1311252.shtml
これは別にext4に限った話ではない。
ちょっと難しいけれど大事な話なのでメモ。

「ファイルを開く、読み込む、変更する、同じ名前で保存する」という一連の作業をプログラムすることはよくある。
一言で言えば、「上書き保存」のことだ。
テキストエディタでファイルを編集するとき、Excel でファイルを編集するときなどがそうだ。
小物アプリケーションを作成するときも、設定ファイルの読み書きでこのようなプログラムコードを書くことはよくある。
そこに落とし穴があるらしい。

世の中には「遅延アロケーション」という技術を使ったファイルシステムがある、XFS、Reiser4、ZFS、ex4 など。
この「遅延アロケーション」が使われていると思わぬファイル消失が生じうるのだ。
https://bugs.edge.launchpad.net/ubuntu/+source/linux/+bug/317781/comments/54
で紹介された例を元にみてみよう。

単純に考えると、ファイルを開いて変更して上書き保存するプログラムは次のようになる。
例1
1) hoge.txt を開いてバッファに読み込み、変更を加える(コード省略)
2) fd = open("hoge.txt", O_WRONLY|O_TRUNC|O_CREAT) --- この時点でディスク上のhoge.txtは空になる
3) write(fd, buff, size-of-buff) --- バッファ上のデータを書き込む
4) close(fd) --- ファイルを閉じる
しかしこれでは、2)から4)の間にシステムがクラッシュすると、データが破壊されてしまう。バッファ上のデータはクラッシュで消えてしまうし、4)まで行っていないのだから、ディスク上のファイルも中途半端なままだ。

なので、もうちょっと工夫して、次のようなプログラムが書かれることが多い。
例2
1) hoge.txt を開いてバッファに読み込み、変更を加える(コード省略)
2) fd = open("hoge.txt.new", O_WRONLY|O_TRUNC|O_CREAT) --- まったく新しいファイルを作る
3) write(fd, buff, size-of-buff) --- バッファ上のデータを書き込む
4) close(fd) --- ファイルを閉じる
5) rename("hoge.txt.new", "hoge.txt")
これなら4)の close までの間にシステムがクラッシュしても、元のhoge.txtが残っているので、まだどうにかなる。
と思ったら大間違い。
ここに遅延アロケーションの落とし穴がある。
遅延アロケーションというのは、ディスクの読み書きを高速化する技術だ。ファイルを書き込む際、すぐに書き込むのではなく、一時的にメモリに保存しておいて、後から読み出しやすいようにメモリの上で整理しておいてから実際にディスクに書き込むようになっている。だから、律儀に毎回ディスクに書き込むFATなどのファイルシステムよりも書き込みは圧倒的に早い。メモリに書き込んでいるだけで、実際のディスクアクセスは生じていないのだから。そして、きりのいいところでディスクに書き込むのだから、そういった意味でも合理的だ。データは読みやすいように整理されているのだから読み込みも早い。
しかし、システムがクラッシュするとこれが裏目に出る。
例2のコードでもう一度脳内検証してみよう。
何らエラーが出ることなく、5)までの動作が無事終わったとしても、まだファイルは実際にはディスクには書き込まれていない。そして、システムにとってきりのいいところでディスクにファイルが書き込まれるのだが、この書き込みの途中でシステムがクラッシュすると、hoge.txt の内容がどうなっているかは分からない。
rename 後、実際の書き出しの動作としては、まず hoge.txt が消され、hoge.txt.new のファイル名が hoge.txt に変更されたという情報が書き出され(メタ情報の変更)、最後に hoge.txt(元の名は hoge.txt.new)の実体がディスクに書き込まれることになる。ファイル名が変更されたところまでは書き出しに成功したものの、実体がディスクに書き込まれる前、あるいは途中でシステムがクラッシュすると、古いファイルは消えてしまい、新しいファイルは中途半端、あるいは空になってしまうというわけだ。

ではどうすればいいのかというと、こうすればいいらしい。
1) hoge.txt を開いてバッファに読み込み、変更を加える(コード省略)
2) fd = open("hoge.txt.new", O_WRONLY|O_TRUNC|O_CREAT) --- まったく新しいファイルを作る
3) write(fd, buff, size-of-buff) --- バッファ上のデータを書き込む
4) fsync(fd) --- 強制書き込み。そしてここでエラーが出ていないか必ずチェックする
5) close(fd) --- ファイルを閉じる
6) rename("hoge.txt.new", "hoge.txt")
こうすれば、hoge.txt.new が必ずディスク上に実際に書き出されることになる。
2)から6)までの間にシステムがクラッシュしてもディスクの上には hoge.txt が残っている。
6)の後にシステムがクラッシュしてもディスクの上のメタ情報の更新に成功していれば、正しい hoge.txt が必ず残されているし、メタ情報の更新に失敗していれば、hoge.txt.new が必ず残っていることになるというわけだ。

しかしこのテクニック、遅延アロケーションという技術が出てくる以前はまったく不要だったものだ。もちろん、このテクニックを採用していないプログラムはたくさんある。
Windows 標準の NTFS や FAT では今のところ遅延アロケーションは採用されていないので、こういったクラッシュ対策は必要ないのだが、将来もそうとは言い切れない。
性能向上のために、NTFS に遅延アロケーションが取り入れられる可能性もある。

そうなった時に考えればいい話といえなくもないが、どーすんだろーね。

テーマ

関連テーマ 一覧


月別リンク

ブログ気持玉

クリックして気持ちを伝えよう!
ログインしてクリックすれば、自分のブログへのリンクが付きます。
→ログインへ

トラックバック(0件)

タイトル (本文) ブログ名/日時

トラックバック用URL help


自分のブログにトラックバック記事作成(会員用) help

タイトル
本 文

コメント(0件)

内 容 ニックネーム/日時

コメントする help

ニックネーム
本 文
ファイルを保存するプログラムコード 民芸的プログラミング 〜ソフトウェア開発日記〜/BIGLOBEウェブリブログ
文字サイズ:       閉じる