在这个测试中,为什么先调用 fsync() 时 rename() 需要更长的时间?
环境:btrfs、机械硬盘、Debian 9 容器,在内核 5.0.17-200.fc29.x86_64 上运行。
测试命令:dpkg -r linux-image-4.9.0-9-amd64 >/dev/null 2>&1 && sync && time perf_4.9 trace --no-inherit -s dpkg $FORCE_UNSAFE_IO -i linux-image-4.9.0-9-amd64_4.9.168-1_amd64.deb && time sync
FORCE_UNSAFE_IO=""
比较vs的结果FORCE_UNSAFE_IO="--force-unsafe-io"
。
dpkg (31632), 374488 events, 100.0%
syscall calls total min avg max stddev
(msec) (msec) (msec) (msec) (%)
--------------- -------- --------- --------- --------- --------- ------
fsync 3442 14849.586 0.002 4.314 149.959 4.11%
rename 8463 14573.509 0.003 1.722 358.675 4.80%
wait4 7 8043.762 0.004 1149.109 8028.468 99.78%
read 44025 2151.135 0.000 0.049 3.732 0.57%
open 19301 213.628 0.002 0.011 0.375 0.90%
write 7846 165.460 0.003 0.021 0.149 0.42%
sync_file_range 6834 96.513 0.001 0.014 0.822 2.20%
...
real 0m41.703s
user 0m9.709s
sys 0m6.586s
real 0m0.162s
user 0m0.000s
sys 0m0.003s
dpkg (1919), 334232 events, 100.0%
syscall calls total min avg max stddev
(msec) (msec) (msec) (msec) (%)
--------------- -------- --------- --------- --------- --------- ------
wait4 7 8290.981 0.007 1184.426 8279.676 99.84%
read 44399 2168.096 0.000 0.049 2.146 0.50%
fsync 25 653.530 0.006 26.141 68.754 8.65%
rename 8463 522.282 0.003 0.062 69.620 22.53%
open 12467 163.671 0.002 0.013 0.217 0.97%
write 7846 160.979 0.003 0.021 0.356 0.50%
sync_file_range 3417 89.676 0.010 0.026 0.841 2.05%
...
real 0m13.498s
user 0m9.643s
sys 0m5.517s
real 0m0.146s
user 0m0.000s
sys 0m0.004s
当前的策略dpkg
(例如在 Debian 9 中)比您可能意识到的更为复杂。但我不确定这是否真的会影响这个案子。如果您想了解更多详细信息,这个问题有一些背景:AIO fsync 可以提高 dpkg 性能吗?
我不知道这是否相关,但我发现在某些文件系统上 fsync() 也可以有效地同步目录。这是为了确保在 fsync() 返回之前新创建的文件在磁盘上可见。我在某处读到这在 ext2 上不会发生,但在 ext4 上确实会发生。作为部分证据,请参见ext4:这次让 fsync 同步无日志中的父目录
如果您对跟踪sync
时间感到惊讶,我可以确认,通过修补dpkg
以使用全局sync() 调用替换单个fsync() 调用似乎可以将总体时间降低到大约13 秒。我在我的系统上没有发现任何不足之处。 dpkg
由于其他潜在的副作用,我刚刚停止使用这种方法。[1][2]
答案1
根据提交描述,我预计 rename() 延迟是由Btrfs:记录新名称后同步日志。这是在内核 v4.19 中添加的。
使新文件名的记录(在创建硬链接或重命名时发生)保留在日志中。
这种方法不仅更简单,[...]而且还为我们提供了与 ext4、xfs 和 f2fs(可能还有其他文件系统)相同的行为。
我不相信第二句话是正确的!
公平地说,我应该指出的dpkg
是,在将包记录为正确安装之前,忘记了 fsync() 包含文件的目录。但这种 btrfs 行为与 Linux 的其他部分并不完全匹配。
我不相信 XFS 会在 rename() 中同步新的目录条目(即故意等待它被持久化)。我对 XFS rename() 内任何同步写入的假设部分基于此线程:https://marc.info/?l=linux-xfs&m=139863577410237&w=2
对于 ext4,我提到的证据表明fsync()可能会在新目录条目返回之前同步它。但我不相信 ext4 的 rename() 会这样做。
我链接到最近的讨论AIO fsync() 操作,以及它们如何实现元数据更新的高效批处理。关于假设的 AIO rename() 还没有太多讨论,因为通常的假设是 rename() 不是同步操作!
(总的来说,btrfs 对我来说有点可疑。即,我看到这个数据完整性错误修复是在过去的几个版本中,而且它并不是唯一听起来可怕的修复变更日志对于这些版本)。
我认为 rename() 延迟必须由BTRFS_NEED_LOG_SYNC
最后一行返回的btrfs_log_new_name()。
我发现这个的方法是使用关闭CPU时间。它通过堆栈跟踪聚合等待时间。堆栈跟踪如下所示:
io_schedule_timeout
wait_for_completion_io
write_all_supers
btrfs_sync_log
btrfs_sync_file
do_fsync
__x64_sys_fsync
do_syscall_64
entry_SYSCALL_64_after_hwframe
- dpkg (23528)
9735954
io_schedule_timeout
wait_for_completion_io
write_all_supers
btrfs_sync_log
btrfs_rename2
vfs_rename
do_renameat2
__x64_sys_rename
do_syscall_64
entry_SYSCALL_64_after_hwframe
- dpkg (23528)
9147785
io_schedule
bit_wait_io
__wait_on_bit
out_of_line_wait_on_bit
write_all_supers
btrfs_sync_log
btrfs_sync_file
do_fsync
__x64_sys_fsync
do_syscall_64
entry_SYSCALL_64_after_hwframe
- dpkg (23528)
4478158
io_schedule
bit_wait_io
__wait_on_bit
out_of_line_wait_on_bit
write_all_supers
btrfs_sync_log
btrfs_rename2
vfs_rename
do_renameat2
__x64_sys_rename
do_syscall_64
entry_SYSCALL_64_after_hwframe
- dpkg (23528)
4376109