コンピューターサイエンス:アルゴリズム / OS / ネットワーク / DB
ジャーナリングと耐障害性
停電したとき、ファイルは半分だけ書かれた状態で残るのか
保存を押した瞬間に停電したとします。素朴には「途中まで書けたファイル」が残りそうですが、本当に怖いのはファイルの中身ではなく、管理側のほうです。
新しいファイルを 1 つ作るとき、ディスクへの書き込みは 1 回では終わりません。中身のブロックを書き、そのブロックを使用中だと記録し、管理情報を書き、親ディレクトリの表に名前を足す。この 4 つのどこで電源が落ちるかで、残る壊れ方が変わります。
- 名前は登録されたのに、ブロックが空き扱いのまま。次に作られたファイルが同じ場所を平気で上書きする
- ブロックは使用中なのに、どの名前からも辿り着けない。容量だけが永久に減る
中身が半分なのは書き直せば済みますが、こちらは無関係なファイルまで巻き込みます。
やることを、やる前に書いておく
昔の UNIX は、起動時にファイルシステム全体を走査して矛盾を突き合わせていました。fsck です。正しく直りはしますが、容量に比例して時間がかかり、数 TB では数十分ディスクが使えません。
ジャーナリングはこれを二段書きで解決します。本体を触る前に、これから行う変更の一覧をディスク上のジャーナル領域へ書き、書き終えた印を付けます。そのうえで本体へ反映し、終わったらジャーナルの該当分を捨てます。
停電から復帰したとき、OS はジャーナルだけを見ます。印があるものは本体への反映をやり直し、印が無い書きかけは無かったことにします。どちらに転んでも矛盾は残りません。確認する範囲がジャーナル領域だけなので、復旧は容量に関係なく数秒で終わります。
同じ問題を別の角度から解く方式もあります。書き換えるときに元の場所を上書きせず、空いている場所へ新しく書き、最後に「どこを指すか」だけを差し替える。落ちた時点で指しているのは常に古い状態か新しい状態のどちらかなので、中途半端が生まれません。APFS や ZFS の考え方です。
守られるのは整合性であって、中身ではない
ext4 の既定では、中身のブロックそのものはジャーナルに通さず、管理情報だけを通します。中身まで二重に書くと、書き込み量が丸ごと倍になるからです。
つまりジャーナリングが約束しているのは「ファイルシステムが矛盾した状態にならないこと」までで、「あなたが書いたデータが残ること」ではありません。アプリの write はページキャッシュに溜まり、数秒遅れてディスクへ向かいます。その間に落ちれば、数秒分は消えます。
c
write(fd, buf, n);
fsync(fd); // ディスクに焼き付くまで、ここで待つ確実に残したいなら fsync を呼び、返ってくるまで待つしかありません。データベースのコミットが 1 件ずつだと遅いのは、この待ちを毎回踏んでいるからです。だから多くの DB は、短い間に届いた複数のコミットをまとめて 1 回の fsync にする仕組みを持っています。