The Linux 5.10 release included a change that is expected to significantly increase the performance of the ext4 filesystem; it goes by the name "fast commits" and introduces a new, lighter-weight journaling method.
Linux 5.10 版本中包含了一个有望显著提高 ext4 文件系统性能的改动,人们称它为 "fast commits (快速提交)",加入了一种新的、更轻量级的日志方法。让我们来看一下这个功能具体是如何工作的、谁能从中受益、以及什么场景下适用。
Ext4 是一个日志文件系统(journaling filesystem),这类文件系统始终在努力确保文件系统结构(filesystem structures)在磁盘上始终保持一致性。对文件系统进行的某个单个操作(从用户的角度来说的)在底层可能需要对文件系统进行多个改动,只有当所有这些更改都记录在磁盘上之后,文件系统才达到了一致状态(coherent)。如果在这些操作的过程中发生断电或系统崩溃(crash),那么数据以及文件系统结构(甚至包括不相干的文件)都有可能损坏。日志(journaling)功能就可以通过在磁盘上独立的日志区域中记录维护的事务(transaction)日志来防止文件系统损坏。在突然断电之后,系统启动时恢复流程就可以根据日志来重新进行文件系统操作,从而将文件系统恢复到具有完好的一致性。
ext4 journal 包括了与正在进行的操作相关的 metadata 的改动,但不一定也包括有关的数据改动。mount 选项中可以指定选择三种日志模式中的一种,ext4 内核文档中有详细描述。 data=ordered
,是缺省设置,会要求 ext4 在将相关的 metadata 提交到日志之前先写入所有数据。它不会将数据本身放入日志中。 data=journal
选项则是要求在数据写入主文件系统之前,先将所有数据写入日志,这里有个副作用就是它禁用了延迟分配(delayed allocation)和 direct I/O 功能。最后一种 data=writeback
则放宽了限制,允许在 metadata 提交到日志之后再将数据写入文件系统。
ext4 还有一个重要功能就是延迟分配(delayed allocation),即文件系统在应用程序希望写入数据的时候,不是立刻就分配存储空间 block,而是等到该数据真正要写入磁盘的时候才分配。这种做法的理论依据是可以等应用程序完成对文件的这些操作之后,再一次性分配实际需要的数据块。这个优化减少了那些临时小文件相关的不必要的操作,可以积累写入操作来批量处理,也有助于确保数据空间的分配连续。另一方面来说,数据写入磁盘的时间可能会被延迟一分钟左右(这是缺省配置的情况)。在默认的 data=ordered
配置之下,只有在所有待处理的数据都被 flush 写入之后才会写入日志记录,因此延迟分配功能可能会导致日志的写入被延后。为了保证数据被真正写入磁盘,应用程序使用 fsync()或 fdatasync()这些系统调用,来将数据(和日志)立即写入。
人们可能认为,在这种情况下,我们可以在 commit (提交,意指数据写入)路径中进行多种优化。实际上确实可以这么做。在这篇 USENIX'17 论文 (https://www.usenix.org/system/files/conference/atc17/atc17-park.pdf) 中,Daejun Park 和 Dongkun Shin 证明了当前的 ext4 日志方案会引入显著的延迟,因为 fsync() 会导致大量与它目的无关的 I/O 操作。他们提出了一个更快的方案,主要是利用了写到日志中的一些 metadata 可以根据被写入的 inode 中的改动来反推出来,从而可以只将跟当前 transaction 相关文件描述符的 commit 操作进行 commit。他们的优化是针对 data=ordered
模式进行的。
Harshad Shirwadkar 实现的 fast-commit 改动,就是基于 Park 和 Shin 的工作。他为 fast commit 实现了一个额外的日志,但是简化了 commit path (提交路径)。现在文件系统中会有两个日志:快速提交日志(fast-commit journal)用来记录可以优化的操作,常规日志(regular journal)则用于 "standard commits(标准的提交操作)" 并且处理方式没有改变。fast-commit journal 中只记录了自从上一次 standard commit 之后所执行的操作。
Ext4 使用了一个名为 "Journaling Block Device 2" (JBD2)的通用日志层(generic journaling layer),具体在磁盘上的存储格式可以从 ext4 wiki 上查到。JBD2 以 blocks 作为执行单位,所以当它 commit 一个 transaction 时,这个 transaction 中包括了所有被改动的 block。一个改动其实可能会导致多个 block 发生变化,比如 inode table 和 block bitmap 就可能需要同时被修改。
而 fast-comit journal 则包含了文件级的变化,因此它的格式更加紧凑。可以计算得到的信息都被省略掉了,在 patch 邮件中有描述:
例如,如果给一个 inode 增加了一个新的 extend,那么只需要根据这个 extent 的信息和相应的 inode 信息就能得出 inode table、block bitmap、group descriptor 和 superblock 的相应变动。
在根据这个日志来恢复磁盘数据的过程中,文件系统必须重新计算 inode 改动所导致的所有发生了变化的 block,并修改磁盘上所有受影响的数据结构。这需要为对文件的每个操作中都要走一条特殊的代码路径,现在这些功能还没有全部实现好。fast-commit 功能目前可以支持 link 和 unlink 一个目录、创建 inode 和 目录、向 inode 中添加 block 和从 inode 中删除 block、以及录制一个今后可以重放(replay)的 inode。
Fast commit 是对 standard commit 路径的补充,而不是替代,两者是相互配合的。如果 fast commit 不能处理某个操作,文件系统就会回到 standard commit 路径。例如,对扩展属性(extended attributes)的改动就需要走 standard commit 路径。在恢复数据的过程中,JBD2 首先会对 standard transaction 操作重新执行,然后再让文件系统来对 fast commit 进行恢复操作。
fast-commit 的这个优化是为了帮助那些频繁使用 fsync()的应用程序,并且确保数据的完整性。我们查看 fsync()和 fdatasync()的 man 页面时,会发现这些系统调用只保证跟这个文件描述符的数据会被写入。而 ext4 文件系统结构导致的一个副作用,就是所有的文件描述符的所有等待处理的数据和 metadata 都将被 flush 出去。这就产生了大量的 I/O 动作,而这些动作实际上并不是当前的 fsync()或 fdatasync()调用所需要实现的目标。
这个副作用导致了论文和实现之间还是有差异的:一个 fast commit 仍然可能包含一些针对其他文件的改动。在 patch review 过程中,Jan Kara 问道,为什么还会有不相关的改动被 commit。Shirwadkar 回答说,在 patch 的早期版本中,他确实只会将相关文件的 transaction 进行 commit。然而,测试下来有一些现有的测试程序会出错,而这些测试程序都把 fsync() 当做保证全部数据都被写入的屏障了,所以他后来就退而求其次改成了现在这个样子。
Ted Ts'o 评论说,当前这个版本的 patch set 确实保证了行为没有发生变化,但他可以看到有一些工作场景下是 "并不强求利用 fsync(2) 来将不相关的文件也要确保写入的,这样可以提供显著的性能提升"。他补充说未来的解决方案可能会是创建一个新的系统调用,参数就是一组文件描述符,来确保它们都会一起同步写入。目前,应用程序开发者应该根据 POSIX 定义来写代码,而不应该依赖现在的 fsync() 特有的副作用,因为这一点在未来可能会发生变动。
fast commit 需要在创建文件系统时就打开的,所以用户必须要重新创建文件系统才能利用这个功能。此外,e2fsprogs 中需要添加一些支持,目前还没有合入主分支中,而是仍在 development 分支。所以,对此功能感兴趣的用户需要自己编译 e2fsprogs,或者等待直到发行版支持这个功能。在启用后,fast commit 相关的信息会显示在一个新增的 /proc/fs/ext4/dev/fc_info 文件中。
在开发工作方面,fast commit 还有许多功能需要添加。包括需要让 operation 粒度更加细、能支持更多目前仍在走 standard commit 的情况。Shirwadkar 还在继续开发可以依据 byte 粒度的 fast commit(而不是目前在用的 block 粒度),这是用在直接访问(DAX,direct-access)模式的,主要用于 persistent memory device。
Shirwadkar 在 patch 中提供的基准测试结果说明,本地文件系统的 filesystem benchmark 性能提升了 20-200%,NFS 场景下的性能提升了 30-75%。我们有理由认为,在那些需要进行许多 fsync() 调用的应用之中,性能的提升会比那些只进行少数 fsync()调用的场景更加显著。但无论如何,fast-commit 功能应该会给 ext4 文件系统带来更好的性能。
从作者的 benchmark 测试数据来看,打开 Fast Commit 特性后,本地 ext4 文件系统有 20% ~ 200% 的性能提升;NFS 场景也有 30% ~ 75% 的性能提升。
Benchmark | Config | w/o Fast Commit | w/ Fast Commit | Delta |
---|---|---|---|---|
FsmarkFsmark | Local, 8 threadsNFS, 4 threads | 1475.1 files/s299.4 files/s | 4309.8 files/s409.45 files/s | +192.2%+36.8% |
DbenchDbench | Local, 2 procsNFS, 2 procs | 33.32 MB/s8.84 MB/s | 70.87 MB/s11.88 MB/s | +112.7%+34.4% |
DbenchDbench | Local, 10 procsNFS, 10 procs | 90.48 MB/s34.62 MB/s | 110.12 MB/s52.83 MB/s | +21.7%+52.6% |
FileBenchFileBench | Local, 16 threadsNFS, 16 threads | 10442.3 ops/s1531.3 ops/s | 18617.8 ops/s2681.5 ops/s | +78.3%+75.1% |
e2fsprogs ,需要 1.46.0 版本才支持 Fast Commit
下载最新的 e2fsprogs 包并编译。
wget https://git.kernel.org/pub/scm/fs/ext2/e2fsprogs.git/snapshot/e2fsprogs-1.46.2.tar.gz
tar -xvf e2fsprogs-1.46.2.tar.gzcd e2fsprogs-1.46.2
./configure
make
2、格式化打开 fast commit 特性。
./misc/mke2fs -t ext4 -O fast_commit /dev/vdc1
dumpe2fs 可以看到已经打开 fast commit: Filesystem features: has_journal ext_attr resize_inode dir_index fast_commit filetype extent 64bit flex_bg sparse_super large_file huge_file uninit_bg dir_nlink extra_isize
ATC-17 论文: https://www.usenix.org/system/files/conference/atc17/atc17-park.pdf
LWN 文章: https://lwn.net/Articles/842385/