Dev Study
← OSとカーネルの土台
レッスン 8 / 10·約 9 分·第10期 OSとカーネルの土台

書いたはずのデータはどこにいるか — fsyncが待っているもの

アプリと DB の下に何が積まれているか(左が下、右が上)
  1. 物理機リージョン/AZ
  2. ハイパーバイザVM exit で捕まえる
  3. VMゲストOS
  4. コンテナnamespace / cgroup
  5. プロセスアプリ / DB今ここ

プロセスがファイルへ書いたものは、どこまで届いているのか。その境界を、規定と公式文書から確かめます。

まず結論

`write` が成功を返してもディスクに届いた保証はない、は第23週で確認済みです。本レッスンはその先。`fsync` が待つのは**デバイスが転送完了を報告すること**で、しかも `fsync` しただけでは、ディレクトリの項目が届いた保証は付きません。

もう一つ。**ext4 のジャーナリングは既定でデータを守っていません。** 通るのはメタデータだけで、公式文書自身が「クラッシュ後、データブロックはいかなる整合状態も保証されない」と書いています。

? 読む前に予測

ext4 は既定でジャーナリングが有効です。電源が落ちたとき、直前に書いたファイルの中身はどうなりますか。

当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。

抽象原理

既に知っていることから始める

第23週『届いたことを確かめる — ACKと再送、そして規定と実装のズレ』で、write が成功を返してもデータはページキャッシュに入っただけで、保持されたと判断するには fsync の戻りを待つ必要があることを見ました。繰り返しません。本レッスンが足すのは、fsync は何を待つのか、fsync してもまだ届いていないもの、そしてジャーナリングは既定ではデータを守っていないことの3つです。

「書いた」が届く先の段。どこまで確かめたのかを区別する
プロセスwrite() が成功を返す
ここで返る(第23週で確認済み)
ページキャッシュカーネルのメモリ上
まだデバイスに無い
fsync() が要求し、戻りを待つ範囲
デバイスへの転送ディスクキャッシュのフラッシュ込み
ディレクトリ項目別の fsync が要る
デバイスが完了を報告して、はじめて fsync が返る
永続化言えるのは fsync の戻り後
  • ・薄い箱が本レッスンの主題。ファイルを fsync しても、その名前は別
  • ・ジャーナルは図の外側で、ファイルシステムの構造を守っている
具体実物

fsync は「書く仕事」を待っているのではない

fsync(2) の規定はこう書いています。

The call blocks until the device reports that the transfer has completed.

待つ相手はデバイスです。カーネル内部の手続きではなく、デバイスからの完了報告。直前にはこうも書かれています。

This includes writing through or flushing a disk cache if present.

ディスク側のキャッシュを書き抜けるかフラッシュするまでが範囲。

この性質は所要時間に現れます。16 MiB を 1 MiB ずつ 16 回書いたあと fsync を呼びました。

16 MiB の write と、その後の fsync(3回、linuxkit VM 上)コマンド出力
-- run 1 --
write 16MiB : 2.946 ms
fsync : 7.308 ms
ratio fsync/write = 2.5x
-- run 2 --
write 16MiB : 2.927 ms
fsync : 7.007 ms
ratio fsync/write = 2.4x
-- run 3 --
write 16MiB : 2.721 ms
fsync : 7.417 ms
ratio fsync/write = 2.7x

16 MiB 分の write が 3 ミリ秒弱で返り、そのあとの fsync 1 回が長い。単発ではさらに開きます。

strace -T(山括弧の中が各呼び出しの所要秒)追跡ログ
openat(AT_FDCWD, "/tmp/s.bin", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE|O_CLOEXEC, 0644) = 3 <0.000054>
write(3, "hellohellohellohellohellohellohe"..., 5000) = 5000 <0.000045>
fsync(3) = 0 <0.000703>

5000 バイトしか書いていないのに、write の 45 マイクロ秒に対し fsync は 703 マイクロ秒。所要時間が「書いた量」で決まっていない。待つのは仕事量ではなく、デバイスの返事だからです。

✓ ここまでの確認

`fsync()` を呼んでそれが返った。このとき確実に言えるのはどれですか。

抽象原理

中身が届いても、名前はまだ届いていない

同じ DESCRIPTION に、見落とされやすい段落があります。

Calling fsync() does not necessarily ensure that the entry in the directory containing the file has also reached disk. For that an explicit fsync() on a file descriptor for the directory is also needed.

ファイルを fsync しても、ディレクトリの項目が届いたとは限らない。ディレクトリの fd への明示的な fsync が別途要る、と規定が述べています。

新規作成では届けるものが2つ。中身と、そこへ到達する名前です。「保存した」の範囲は、どの fd に fsync を呼んだかで決まります。

なお fdatasync() は、後続の読み出しに必要でないメタデータのフラッシュを省きます。規定が挙げる例は st_atime / st_mtime です。

具体実物

本命 — ジャーナリングは既定ではデータを守っていない

ext4 の公式文書 journal.rst は冒頭で目的をこう述べます。

Introduced in ext3, the ext4 filesystem employs a journal to protect the filesystem against metadata inconsistencies in the case of a system crash.

名指しされているのは metadata inconsistencies、メタデータの不整合。続く段落が帰結を書いています。

For performance reasons, ext4 by default only writes filesystem metadata through the journal. This means that file data blocks are /not/ guaranteed to be in any consistent state after a crash.

**既定でジャーナルを通るのはメタデータだけ。**その結果、クラッシュ後にデータブロックはいかなる整合状態も保証されないと公式が明記しています。保証する範囲も限定的です。

guaranteeing the atomicity of whatever gets written through the journal to the disk. The effect of this is to guarantee that the filesystem does not become stuck midway through a metadata update.

ジャーナル経由で書かれたものの原子性と、メタデータ更新の途中で止まらないこと。「中身が無事」とはどこにも書かれていません。

データモードは admin-guide/ext4.rst にあり、data=ordered に (*)(既定の印)が付きます。

admin-guide/ext4.rst のデータモード((*) が既定)設定ファイル
data=journal
All data are committed into the journal prior to being written into the
main file system. Enabling this mode will disable delayed allocation
and O_DIRECT support.
 
data=ordered (*)
All data are forced directly out to the main file system prior to its
metadata being committed to the journal.
 
data=writeback
Data ordering is not preserved, data may be written into the main file
system after its metadata has been committed to the journal.

既定の data=ordered は「データを本体へ先に押し出してから、メタデータをジャーナルに commit する」。順序を決めるだけで、データ自体はジャーナルに入りません。

同じ文書の commit オプションが、混同されやすい2語を書き分けています。

This setting limits the maximum age of the running transaction to 'nrsec' seconds. The default value is 5 seconds. This means that if you lose your power, you will lose as much as the latest 5 seconds of metadata changes (your filesystem will not be damaged though, thanks to the journaling).

既定は5秒。電源を失えば直近5秒ぶんのメタデータ変更は失うが、ジャーナリングのおかげでファイルシステムは損傷しない。「壊れない」と「失わない」は別だと公式が同じ文で書き分けています。

✓ ここまでの確認

`/proc/mounts` を見たら ext4 の行に `data=ordered` が出ていませんでした。どう読むべきですか。

⇄ 2つを見比べる
具体例 A

ext4 の既定のジャーナル(data=ordered)

ジャーナルを通るのはメタデータだけ。データは本体へ先に押し出され、そのあとメタデータが commit されます。公式文書は、クラッシュ後にデータブロックの整合は保証されないと明記しています。

具体例 B

DB編『先に書くログ — 順序を縛ると、書く回数が減る』の WAL

データベースは、データファイルを書き換える前に、その変更を記述したログを先に永続化します。本体への変更は、ログレコードが永続化された後でなければ書けません。記録の対象はデータそのものです。

ext4 のジャーナルと、データベースの WAL。どちらも「先にログを書く」仕組みですが、守っている対象はどう違うでしょうか。

それぞれが「ログに何を書くか」と「クラッシュ後に何を復元できると宣言しているか」を並べてください。

この章のまとめ

  • write が成功を返してもページキャッシュにあるだけ、は第23週で確認済み。本レッスンはその先を扱った。
  • fsync が待つのはデバイスからの完了報告。ディスクキャッシュのフラッシュを含む。実測でも所要時間は書いた量に比例しなかった(比のみ。絶対値は VM 上の値)。
  • ファイルを fsync しても、ディレクトリの項目が届いた保証は付かない。 ディレクトリの fd への明示的な fsync が別途必要。
  • fdatasync が省くのは「後続の読み出しに必要でない」メタデータ(公式の例は st_atime / st_mtime)。
  • 本命。ext4 の既定は data=ordered で、ジャーナルを通るのはメタデータだけ。 公式が「データブロックはクラッシュ後にいかなる整合状態も保証されない」と明記している。
  • ジャーナルの保証は「経由して書かれたものの原子性」と「メタデータ更新の途中で止まらないこと」。「壊れない」と「失わない」は別。
  • /proc/mounts に data=ordered が出ないのは既定だから省略された印。

自分の現場で確かめる

読んだだけでは身につきません。手元の環境で実際に見てみてください。

環境:

理解度チェック

読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。

問題 1 / 3正解: 0

`fsync()` が「待っている」ものとして、規定に照らして最も正確なのはどれですか。

用語集(3語)
ページキャッシュ
ファイルの内容をメモリ上に保持する領域。`write` は既定でここまでで返る
fsync / fdatasync
ファイルの変更をデバイスへ反映させ、完了報告まで待つシステムコール。fdatasync は、後続の読み出しに必要でないメタデータのフラッシュを省く。
ジャーナル(ext4)
メタデータの不整合からファイルシステム自身を守るため、変更を先に書き留める領域。既定ではデータ本体はここを通らない。既定の data=ordered は、データを本体へ先に押し出してからメタデータを commit する。