ext4のnoauto_da_allocオプションの詳細

# まずは結論
Linux-3.18くらいを見て書いてます。
### よくわからない場合
オプションは何も足すな。何も削るな。
### わかった上でパフォーマンス上げたい場合
**noauto_da_alloc** を足す。
### アプリの実装ミスであろうがなかろうが、電源断でファイルが消えるのが怖くて夜も寝られない場合
**journal_checksum** **data=journal** くらい？
(**data=journal**にすると自動で**nodelalloc**も有効になる)
(**nodelalloc**だと**noauto_da_alloc**は意味をなさない)
### 電源断？そんなのいいから最速を目指すぜ
**noauto_da_alloc** **nobarrier** **journal_async_commit** **noatime** **data=writeback** くらい？
・・・**tune2fs(8)**使ってjournalなしにする？むしろext4なんてやめた方が・・・

# はじめに
2009年-2010年頃にいわゆる「Zero Length File Problem」で世間が賑やかになった結果、**noauto_da_alloc** と **nodelalloc** オプションが注目を集めた。ググれば英語の情報はそれなりにあるものの、日本語ではほとんどなかったので、ここに一度まとめておくこととした。

誤解してほしくないのは、下記はすべて電源断(や突然のpanicやリセットやディスク/ケーブル引っこ抜き)の時を想定した話。不正規のshutdownがないとした場合、どんなmountオプションであったとしても「Zero Length File Problem」のようなことは起こらない。

また、ディスク上のデータ化け、CPUの暴走、RAM化けはそもそも考慮外。これらが起こると、Filesystemがどうこうじゃないレベルでデータが壊れる。

# このページを信じてはいけない
- [EXT4のゼロレングス問題(zero-length problem)に対処したマウントを行う](http://d.hatena.ne.jp/ktomoya/20100731/1280545627)

ググると日本語では上記ページが上位にヒットする。通りすがりの方々がコメントで指摘してる通りで、デフォルトは**auto_da_alloc**(==「壊れたアプリ」の面倒を見るためにわざわざKernelで特殊対応)の動作をする。このわざわざ行っている特殊対応を行わないようにするのが**noauto_da_alloc**オプションとなる。

# **noauto_da_alloc** を足すと何が起こるの？
### Documentationを見る
[**Documentation/filesystems/ext4.txt**](https://www.kernel.org/doc/Documentation/filesystems/ext4.txt)にはものすごく具体的なことが書いてある。おそらく開発者自身がいろいろ言い訳したかったんだろう。
> Many broken applications don't use fsync() when 
> replacing existing files via patterns such as
> fd = open("foo.new")/write(fd,..)/close(fd)/
> rename("foo.new", "foo"), or worse yet,
> fd = open("foo", O_TRUNC)/write(fd,..)/close(fd).
> If auto_da_alloc is enabled, ext4 will detect
> the replace-via-rename and replace-via-truncate
> patterns and force that any delayed allocation
> blocks are allocated such that at the next
> journal commit, in the default data=ordered
> mode, the data blocks of the new file are forced
> to disk before the rename() operation is
> committed.  This provides roughly the same level
> of guarantees as ext3, and avoids the
> "zero-length" problem that can happen when a
> system crashes before the delayed allocation
> blocks are forced to disk.

更新されないままのドキュメントが多いから本当はリンクしたくないんだけど、一応日本語の方も。
https://linuxjf.osdn.jp/JFdocs/kernel-docs-2.6/filesystems/ext4.txt.html
> 多くの壊れたアプリケーションは、fd = open("foo.new")/
> write(fd,..)/close(fd)/rename("foo.new", "foo") な
> どのパターン、あるいはそれより邪悪な
> fd = open("foo", O_TRUNC)/write(fd,..)/close(fd)
> パターンで、fsync() を使わないで既存のファイルを入れ
> 替えます。auto_da_alloc が有効な場合、ext4 はこのリ
> ネームによるファイルの入れ替えと truncate によるファ
> イルの入れ替えのパターンを検知し、標準のデータオーダー
> ドモードの場合に、リネーム処理がコミットされる前に新規
> ファイルのデータブロックがディスクに書き込まれるよう、
> 遅延割り当てされたブロックを次のジャーナルコミット時に
> 強制的に割り当てます。この処理でほぼ ext3 と同程度の保
> 証ができ、遅延割り当てされたブロックがディスクに書かれ
> る前にシステムがクラッシュした場合の「長さ 0 になる」
> 問題を (同程度に) 回避できます。

### コードを見る
[super.cのext4_mount_opts](http://lxr.free-electrons.com/source/fs/ext4/super.c?v=3.18#L1379)より、

```c:super.c
1379         {Opt_noauto_da_alloc, EXT4_MOUNT_NO_AUTO_DA_ALLOC, MOPT_SET},
1380         {Opt_auto_da_alloc, EXT4_MOUNT_NO_AUTO_DA_ALLOC, MOPT_CLEAR},
```
なので、**noauto_da_alloc**オプションを入れると**EXT4_MOUNT_NO_AUTO_DA_ALLOC**が立つことがわかる。
マクロ展開されるため、**EXT4_MOUNT_NO_AUTO_DA_ALLOC** ではなく **NO_AUTO_DA_ALLOC** でgrepすると、2箇所ヒットする

1箇所目が[namei.cのext4_rename()](http://lxr.free-electrons.com/source/fs/ext4/namei.c#L3576)

```c:namei.c
3576         if (new.inode && !test_opt(new.dir->i_sb, NO_AUTO_DA_ALLOC))
3577                 ext4_alloc_da_blocks(old.inode);
```
renameで上書きが発生する場合にold側(==名前が変わる方のinode)を対象に**ext4_alloc_da_blocks()**が呼ばれる。

2箇所目が[inode.cのext4_truncate()](http://lxr.free-electrons.com/source/fs/ext4/inode.c?v=3.18#3584)

```c:inode.c
3584         if (inode->i_size == 0 && !test_opt(inode->i_sb, NO_AUTO_DA_ALLOC))
3585                 ext4_set_inode_state(inode, EXT4_STATE_DA_ALLOC_CLOSE);
```

EXT4_STATE_DA_ALLOC_CLOSEは[file.cのext4_release_file()](http://lxr.free-electrons.com/source/fs/ext4/file.c?v=2.6.33#L38)

```c:file.c
 38         if (EXT4_I(inode)->i_state & EXT4_STATE_DA_ALLOC_CLOSE) {
 39                 ext4_alloc_da_blocks(inode);
 40                 EXT4_I(inode)->i_state &= ~EXT4_STATE_DA_ALLOC_CLOSE;
 41         }
```

つまり、truncateでファイルサイズを0にした後closeしたタイミングで、ここでも**ext4_alloc_da_blocks()**が呼ばれる。

[inode.cのext4_alloc_da_blocks()](http://lxr.free-electrons.com/source/fs/ext4/inode.c?v=3.18#L2703)はコメントが長いけどそのまま引用して、

```c:inode.c
2703 int ext4_alloc_da_blocks(struct inode *inode)
2704 {
2705         trace_ext4_alloc_da_blocks(inode);
2706 
2707         if (!EXT4_I(inode)->i_reserved_data_blocks)
2708                 return 0;
2709 
2710         /*
2711          * We do something simple for now.  The filemap_flush() will
2712          * also start triggering a write of the data blocks, which is
2713          * not strictly speaking necessary (and for users of
2714          * laptop_mode, not even desirable).  However, to do otherwise
2715          * would require replicating code paths in:
2716          *
2717          * ext4_writepages() ->
2718          *    write_cache_pages() ---> (via passed in callback function)
2719          *        __mpage_da_writepage() -->
2720          *           mpage_add_bh_to_extent()
2721          *           mpage_da_map_blocks()
2722          *
2723          * The problem is that write_cache_pages(), located in
2724          * mm/page-writeback.c, marks pages clean in preparation for
2725          * doing I/O, which is not desirable if we're not planning on
2726          * doing I/O at all.
2727          *
2728          * We could call write_cache_pages(), and then redirty all of
2729          * the pages by calling redirty_page_for_writepage() but that
2730          * would be ugly in the extreme.  So instead we would need to
2731          * replicate parts of the code in the above functions,
2732          * simplifying them because we wouldn't actually intend to
2733          * write out the pages, but rather only collect contiguous
2734          * logical block extents, call the multi-block allocator, and
2735          * then update the buffer heads with the block allocations.
2736          *
2737          * For now, though, we'll cheat by calling filemap_flush(),
2738          * which will map the blocks, and start the I/O, but not
2739          * actually wait for the I/O to complete.
2740          */
2741         return filemap_flush(inode->i_mapping);
2742 }
```
i_reserved_data_blocksは遅延アロケーションしているブロックの数。これがなければ何もしない。これがあれば**filemap_flush()**を呼ぶ。つまり、IOをスタートさせる(けどIO完了までは待たない)。fsyncを呼ぶのに近いことをしている。

(「時々目的のpageのIOが依存の影響で発生しないこともある」とかコメントに書いてあったけど今は気にしないことにする...)

### まとめると
ext4はデフォルトでは、
- renameで上書きが発生する場合
- truncateでサイズ0にした場合のcloseのタイミング
で
- fsyncに近いこと
をする。ただし書き込み待ちはしない。こういう動きをすることで「Zero Length File Problem」を起こしにくくしている。**noauto_da_alloc**オプションを足すと、この特別対応を行わなくする。

# auto_da_allocがあるから「Zero Length File Problem」はもう起こらないんだよね？
答えはNO、[EXT4 VS. EXT3 AND WHY DELAYED ALLOCATION IS BAD](http://www.pointsoftware.ch/en/4-ext4-vs-ext3-filesystem-and-why-delayed-allocation-is-bad/)に詳しい。

これはKernel(Filesystem)のバグではなくてユーザプログラムのバグなのである。「Many broken applications」は「ext3の時の挙動こそ正しい」と信じて「ext4の動きがおかしい」と言ってるだけ。**auto_da_alloc**で上記2パターンに救済を入れたものの、これだけではext4がext3と近い挙動にはならない。少なくとも遅延アロケーション機能を無効にしないと近い挙動にはならない。

というわけで、「Many broken applications」をそのまま使い続けたい場合**nodelalloc**が推奨されることになる。ただしパフォーマンスが1割前後落ちる。

世の中ext3/ext4だけではないので、結局POSIXレベルで議論する限り、複数のinodeをまたいでデータの更新の順序を保障したい場合、**fsync(2)**や**fsyncdata(2)**をアプリが適切に呼ぶことでしか保障できない。

# あとがき
電源断を含めたデータの保護はものすごく難しい。ストレージのハードウェア(やファームウェア)がやるべきこと、ストレージドライバがやるべきこと、Filesystemがやるべきこと、アプリケーションがやるべきこと、いろいろある。ACIDだトランザクションだ言われる。だからRDBMSがもてはやされるんだと思う。

今後もext4シリーズの記事を書く方向へ続くかもしれない？