らっちゃいブログ

日々の学びと気づきを発信するブログ

ファイル出力したそのデータ、本当にディスクに書き出されてますか?

スポンサーリンク

ファイルに出力したつもりのデータ。それ本当にディスクに書き出されてますか?

たとえば以下のコード。

with open('racchai.txt', 'w') as f:
    f.write('racchai!')
    f.flush()

これまで出会ったほとんどの方が flush を呼び出しているから大丈夫と思っていました。でも、大丈夫ではありません。

この時点ではまだユーザーランドからOSのバッファに入っただけで、ディスクに書き出すかどうかは保証されていません。クラッシュしたら失われてしまうデータです。いわゆるダーティデータと呼ばれるものですね。

これは python に限った話ではありません。 java でも php でも同様です。

ではディスクに書き出すことを保証するにはどうしたらよいのかというと、fsync システムコールを使います。python では os.fsync という関数が提供されており、これを使うことで fsync システムコールが呼び出せるようになっています。

os.fsync の使い方としては、引数にファイルの fd を渡すだけです。

import os

with open('racchai.txt', 'w') as f:
    f.write('racchai!')
    f.flush()
    os.fsync(f.fileno())

os.fsync は処理が終わるまでブロックするため、実行後にはディスクへの書き出しが完了しています。これでようやく枕を高くして寝れますね。

っと、安心するのはまだ早いです。この fsync はデータをディスクに書き出すと同時にファイルのメタデータ(inode情報)も更新するようになっています。つまり、データの書き出しと合わせて2回のディスクアクセスが発生してしまいます。

滅多に通らない処理ならいいですが、頻繁に呼び出される場合は無視できません。そういったケースでは fdatasync を呼び出すべきです。

fdatasync も fsync と同様、データを強制的にディスクへ書き出すためのシステムコールになります。違いとしては、fdatasync はデータ出力のみを行うようになっています。つまり、fdatasync を使用することで不必要なメタデータ書き込み処理を避けることができるのです。使い方は fsync と同じです。

import os

with open('racchai.txt', 'w') as f:
    f.write('racchai!')
    f.flush()
    os.fdatasync(f.fileno())

これでディスクアクセスのオーバーヘッドを最小限に抑えた上で、障害に強いプログラムとなりました。

注意してほしいのは、闇雲に fsync/fdatasync を呼び出せばよいというわけではない点です。失われてもよい一時ファイルのようなものまで fsync するのは時間の無駄ですので、使い所を考えて呼び出すようにしましょう。

Linuxシステムプログラミング

Linuxシステムプログラミング