« ディスクが1回転する間に複数回 fdatasync する方法について | メイン | Q4M - MySQL 上で動作するメッセージキュー »

2008年01月04日

ウェブアプリケーションにおけるHDDの正しい使い方

 データベース等のソフトウェアは一般に、停電やOSのクラッシュ時にデータが破壊されないよう、HDD へデータ保存が完了したか確認しながら処理を行うようになっています。その目的を果たすためにどのような API が OS によって提供されているか、少し勉強し直すことにしました。

 下表のうち、赤い部分がデータの永続性が保証されない危険な手法、青い部分が安全な手法です。したがって、各行において出来るだけ左側の (高速側の) 、そして言うまでもなく青い色の同期手法を使っていることが望ましいということになります。

OS openモード HDD または RAID 内の書込先
キャッシュ (高速) プラッター (低速)
Linux注1 同期 open(O_SYNC) fdatasync
非同期 N/A fdatasync
Linux+UPS注2
Linux+RAID注3
同期 open(O_SYNC) N/A
非同期 fdatasync N/A
Windows 同期 CreateFile(FILE_FLAG_WRITE_THROUGH) FlushFileBuffers
非同期 N/A FlushFileBuffers
Windows+UPS 同期 CreateFile(FILE_FLAG_WRITE_THROUGH) FlushFileBuffers
非同期 N/A FlushFileBuffers
Windows+RAID注3 同期 CreateFile(FILE_FLAG_WRITE_THROUGH) N/A
非同期 FlushFileBuffers N/A
Mac OS X 同期 N/A
非同期 fsync fcntl(F_FULLFSYNC)
Mac OS X+UPS 同期 N/A
非同期 fsync fcntl(F_FULLFSYNC)
Mac OS X+RAID注3 同期 N/A
非同期 fsync または fcntl(F_FULLFSYNC) N/A
Solaris 同期 open(O_SYNC) ioctl(DKIOFLUSHWRITECACHE)
非同期 fdatasync ioctl(DKIOFLUSHWRITECACHE)
Solaris+UPS 同期 open(O_SYNC) ioctl(DKIOFLUSHWRITECACHE)
非同期 fdatasync ioctl(DKIOFLUSHWRITECACHE)
Solaris+RAID注3 同期 open(O_SYNC) N/A
非同期 fdatasync または ioctl(DKIOFLUSHWRITECACHE) N/A

 実際に表を起こしてみて、以下のようなことがわかりました。

開発側の注意すべき点

  • 同一OSの場合でも環境によって最適な同期手法は異なる
  • したがって、同期手法はハードコードすべきではなく、設定値として取り扱うべき
  • Windows 向けのプログラムにおいては、(UPS を使っている場合に高速な同期が不可能な) 非同期モードによる CreateFile を使うべきではない注4

運用側の注意すべき点

  • データベース等の同期手法を正しく設定すべき
  • UPS つきなのに F_FULLFSYNC しちゃってるとか
  • また、使用するソフトウェアでどのような同期手法が選択可能かを確認した上で、運用環境を構築すべき
  • たとえば、FlushFileBuffers を呼び出しているミドルウェアを使う場合は UPS よりもバッテリバックアップキャッシュつき RAID の方が、速度向上の度合いが大きくなる可能性が高い

 ところで、HDD の内蔵キャッシュと UPS による永続性の確保と、RAID のバッテリバックアップキャッシュによる永続性の確保を、同列に扱うことに問題があったりするんでしょうか? 自分の理解の範囲外に何かそのような要素がひょっとしたらあるのかも、と、上の表に色を塗りながら、そこが一番気になりました。

注1: hdparm -W 0 として HDD の内蔵キャッシュをライトスルーモードに設定
注2: hdparm -W 1 として HDD の内蔵キャッシュをライトバックモードに設定
注3: バッテリバックアップつきのキャッシュを搭載した RAID カードを想定
注4: Windows 2000 においては FlushFileBuffers の動作を変更するパッチが提供されている

投稿者 kazuho : 2008年01月04日 16:13 このエントリーを含むはてなブックマーク このエントリーを含むはてなブックマーク

トラックバック

このエントリーのトラックバックURL:
http://labs.cybozu.co.jp/cgi-bin/mt-admin/mt-tbp.cgi/1726

コメント

H2 Database の作者さんによると、
http://www.h2database.com/html/advanced.html#durability_problems
fsync() は(まちがいなく、fdatasyncを含むと思いますが)ディスクの回転数分しか fsync() を発行できないくせに、実際に、電源コードを引き抜いてテストしてみると、多くのハードディスクは実際には書き込みが完了していない、と主張していますが、実際どうなのでしょう?

投稿者 yukoba : 2008年01月05日 08:01

具体的にどこの部分の表現でしょうか。自分にはそのような記載のある箇所は見つけられませんでした。

投稿者 kazuho : 2008年01月05日 09:40

http://www.h2database.com/html/advanced_ja.html#durability_problems
面倒なので、日本語訳の方です。

> 完全な永続性とは、全てのコミットされたトランザクションは電源異常に耐えられる、ということを意味します。いくつかのデータベースは、永続性を保証すると主張していますが、このような主張は誤っています。永続性テストはH2、HSQLDB、PostgreSQL、Derbyに対して実行されました。これらの全てのデータベースは、時々コミットされたトランザクションを失います。

> デフォルトでは、MySQLはそれぞれのコミットごとに fsync を呼びます。それらのメソッドのうちひとつを使用している時、毎秒およそ60件だけが実行され、使用されているハードドライブのRPM速度と一貫性があります。残念ながら、FileDescriptor.sync() または FileChannel.force() を呼んだ時でさえもデータは常にハードドライブに存続するとは限りません。なぜなら、多くのハードドライブは fsync() に従わないからです: http://hardware.slashdot.org/article.pl?sid=05/05/13/0529252 内の"Your Hard Drive Lies to You" をご覧下さい。Mac OS X では、fsync はハードドライブバッファーをフラッシュしません: http://lists.apple.com/archives/darwin-dev/2005/Feb/msg00072.html そのため状況は混乱していて、 問題があることをテストは証明しています。

あたりです。

少なくとも、H2 Database さんの文章を誤読していないつもりですが、主張していることが本当かどうかは、実際に実験してないので、よくわかりません。

あと、okuさんの、

> データベース等のソフトウェアは一般に、停電やOSのクラッシュ時にデータが破壊されないよう HDD へデータ保存が完了したか確認しながら処理を行うようになっています。

ですが、UPSを使っていても、サーバー本体の電源が故障したら「停電」になりますし、UPS使っていても「OSのクラッシュ」対策になりませんし、UPSを使っている場合に、青にしてしまうのは、まずいと思います。まぁ、そもそも、ハードディスクに書き込み命令を送っても、実際に読んでみないと(ベリファイやコンペアしないと)、本当に書き込まれたかなんてわからないわけですし、こんなの程度問題ですけどね。

あと、これまた、さらにちゃんと確認していないんですが、複数のスレッドがから並列で書き込んでいる時、どうも、MySQL はディスクの回転数以上のトランザクションを裁けるような気がします。ディスク1回転あたりに、2つ以上の書き込みをまとめて行うことをしていると思います。本当に未確認ですいません。

投稿者 yukoba : 2008年01月05日 11:28

> 面倒なので、日本語訳の方です。

ありがとうございます。確かにそのように読めますね。にわかには信じがたい話ですし、仕様としてプラッタに同期する方法を使っているのにデータロスが発生するとしたら、それは (OSかHDDかRDMSの) バグなんじゃないかと思いますけど。
自分が知っている範囲だけでも、Windows 2000 の特定バージョンでの FluhWriteBuffers の誤動作 (本文のリンク参照) や、SQLite のジャーナルが消失する問題 (ディレクトリエントリを fsync してなかった) といったものもありましたし、実際に試してみて適切な手法を取るしかないんでしょうね。

次に、UPS がある場合に、ディスク内のキャッシュへの書き出しを青色にしている件ですが、

> UPSを使っていても、サーバー本体の電源が故障したら「停電」になりますし、

これは例えば RAID のバッテリが故障した場合にも同じ議論が成り立ちますよね。永続性の確保の有無という意味では、系内の故障は考えず、系外からの電源断に対処できているかだけを検討すればいいと思っています。一方で、故障率や SPOF をどうするかといった、高可用性の議論は別に必要だとは思います。

> UPS使っていても「OSのクラッシュ」対策になりませんし

OS がクラッシュしても HDD のライトキャッシュは消えず、順次プラッターに書き込まれると理解しているのですが、違うのでしょうか? 実はここが一番自信のないところなのですが...

> 複数のスレッドがから並列で書き込んでいる時、どうも、MySQL はディスクの回転数以上のトランザクションを裁ける

はい。InnoDB はそのような機能 (Group Commit と言ったはず) を実装しています。また、OS と HDD 間の通信のレベルで見ても、同期書き込みが複数回できることもあります。

投稿者 kazuho : 2008年01月05日 14:30