AIO fsync 可以提高 dpkg 性能吗?

AIO fsync 可以提高 dpkg 性能吗?

Debian 包管理器可以dpkg通过使用 AIO fsync() 操作之一而不是sync_file_range() + fsync() 来获得显着的性能改进吗?

[提议的] fsync2() API 本质上与现有的 AIO_FSYNC/AIO_FDSYNC API 相同,只是它是同步的,而这是应用程序想要避免的。

我反对[使用] AIO_FSYNC 的唯一论点是“该实现只是一个工作队列”,这在很大程度上是无意义的,因为它独立于文件系统实现,但允许自动内核端并行化所有发出的 fsync 操作。这允许文件系统在完成并发 fsync 操作时自动优化掉不必要的日志写入 - 当用户应用程序从大量进程/线程同时运行 fsync() 时,XFS、ext4 等已经这样做了......

这个简单的实现允许在 XFS 上执行简单的“使用 aio fsync 解压”工作负载(即“批量写入许多 4kB 文件和 aio_fsync(),在分派新批次之前退出已完成的 fsync()”)工作负载大约 2000 个文件/秒(同步写入 IO 延迟范围)到超过 40,000 个文件/秒(后端存储上的写入 IOPS)。

--戴夫·钦纳

示例工作负载与apt-get install或相似dpkg -i(部分取决于已安装软件包中文件的大小:-)。 dpkg在将它们重命名到位之前,必须有效地 fsync() 所有解压的文件。

dpkg已根据 Ted T'so 的建议进行了优化。优化是在某些点添加对sync_file_range() 的调用。这个系统调用做了不是提供与 fsync() 相同的保证。请阅读文档同步文件范围()并注意突出的警告:-)。

这些操作都不会写出文件的元数据。因此,除非应用程序严格执行已实例化磁盘块的覆盖,否则无法保证数据在崩溃后可用。

dpkg在写入每个文件后立即触发数据写回,使用SYNC_FILE_RANGE_WRITE.它首先写入包的所有文件。然后第二次遍历文件,等待使用SYNC_FILE_RANGE_WAIT_BEFORE、调用 的数据写回fsync(),最后将文件重命名到位。

查看提交:

我的假设是,并行化 fsync() 操作可以通过允许更有效的批处理来提高性能元数据写入,特别是批处理相关的屏障/磁盘缓存刷新,这是确保磁盘上的元数据始终一致所必需的。

编辑:看来我的假设太简单了,至少在使用 ext4 文件系统时:

第二个系列的sync_file_range()调用以及操作 SYNC_FILE_RANGE_WAIT_BEFORE将阻塞,直到先前启动的写回完成。这样基本就保证了延迟分配已经得到解决;也就是说,数据块已被分配和写入,并且索引节点已更新(在内存中),但不一定被推送到磁盘。

[fsync()] 调用实际上会强制将 inode 写入磁盘。 对于 ext4 文件系统,第一个 [fsync()] 实际上会将所有 inode 推送到磁盘,并且所有后续的 [fsync()] 调用实际上都是空操作(假设文件“a”、“b”和“c”都位于同一文件系统上)。但这意味着它将(重量级)jbd2 提交的数量降至最低。

它使用 Linux 特定的系统调用 ---sync_file_range() --- 但结果应该是所有文件系统的全面更快的性能。因此,我不认为这是针对 ext4 的 hack,尽管它可能确实使 ext4 的速度比任何其他文件系统更快。

--特德·索

其他一些文件系统可能会受益于使用 AIO fsync() 操作。

bcachefs(正在开发中)声称比 ext4 更好地隔离不同文件之间的 IO。所以测试可能特别有趣。

听起来好像 ext4 对于纯 AIO fsync() 模式可能没有那么优化(我猜其他文件系统也可能有相同的约束)。如果是这样,我想可以首先执行所有相同的sync_file_range()调用,然后开始所有AIO fsync()操作作为第二轮,并通过将所有文件重命名为fsync()来完成操作完成。


老的:

这种调查的第一步应该是测量:-)。

可以使用 禁用 fsync() 部分echo "force-unsafe-io" > /etc/dpkg/dpkg.cfg.d/force-unsafe-io

到目前为止,我尝试在 Debian 9 容器中apt-get install运行。strace -f -wc例如,使用“unsafe io”安装aptitude包,只有 495 个同步 fsync() 调用。正常安装时aptitude,有 1011 个 fsync() 调用。 “unsafe io”还禁用了该SYNC_FILE_RANGE_WAIT_BEFORE调用,将sync_file_range()调用的数量从1036减少到518。

然而,尚不清楚这是否会减少平均时间。如果确实如此,那么它似乎也只不过是运行之间的随机变化而已。到目前为止,我在机械 HDD 上的 ext4 和 XFS 上进行了测试。


apt-get表示 518 个解压文件的总大小为 21.7 MB(请参见下面的输出)。

关于 495 fsync() 调用,即使在请求“不安全 io”时,该调用仍然存在:

在 ext4 上,strace 输出显示剩余的 fsync() 调用所花费的时间约为 11 秒。在XFS上,相应的数字约为7秒。在所有情况下,这是安装所花费的大部分时间aptitude

因此,即使“不安全的 io”对安装带来了小小的改进aptitude,似乎您也需要/var安装在比系统其他部分更快(延迟更低)的设备上,然后差异才会真正明显。但我对优化这个利基案例不感兴趣。

运行strace -f -y -e trace=fsync,rename显示,对于剩余的 fsync() 调用,其中 2 个是 on /etc/ld.so.cache~,其中 493 个是对/var/lib/dpkg/包数据库内的文件。

318 的 fsync() 调用位于/var/lib/dpkg/updates/.这些是 dpkg 数据库的增量/var/lib/dpkg/status。在 dpkg 运行结束时,增量将汇总到主数据库中(“检查点”)。


The following NEW packages will be installed:
  aptitude aptitude-common libboost-filesystem1.62.0 libboost-iostreams1.62.0 libboost-system1.62.0 libcgi-fast-perl libcgi-pm-perl
  libclass-accessor-perl libcwidget3v5 libencode-locale-perl libfcgi-perl libhtml-parser-perl libhtml-tagset-perl libhttp-date-perl
  libhttp-message-perl libio-html-perl libio-string-perl liblwp-mediatypes-perl libparse-debianchangelog-perl libsigc++-2.0-0v5 libsqlite3-0
  libsub-name-perl libtimedate-perl liburi-perl libxapian30
0 upgraded, 25 newly installed, 0 to remove and 0 not upgraded.
Need to get 0 B/6000 kB of archives.
After this operation, 21.7 MB of additional disk space will be used.

答案1

这个问题表明这对 ext4 或 XFS 没有帮助。

我还测试了安装一个更大的包 ( linux-image-4.9.0-9-amd64)。不管怎样,它似乎仍然需要同样的时间--force-unsafe-io

外部2

在 ext2 上,--force-unsafe-io安装时间linux-image从 50 秒减少到 13 秒。

我运行测试的内核是5.0.17-200.fc29.x86_64,它使用CONFIG_EXT4_USE_FOR_EXT2.

我使用用户空间 aio_fsync() 实现测试了 ext2。然而,最好的改进并不依赖于使用 AIO fsync()。

我的进步实际上是由于副作用。我已将 dpkg 更改为首先执行所有 fsync() 操作,然后执行所有 rename() 操作。而未打补丁的 dpkg 在每次 fsync() 之后调用 rename()。我使用的 AIO 队列深度高达 256。队列深度为 1 的 AIO fsync() 明显慢于同步 fsync() - 似乎存在一些开销。最好的改进还需要SYNC_FILE_RANGE_WRITE首先完成所有原始操作。改进版本安装时间linux-image约为 18 秒。

这个操作顺序实际上是 Ted T'so 最初建议的:-D。发生的情况是CONFIG_EXT4_USE_FOR_EXT2, fsync() 也有助于同步父目录。您希望首先执行所有文件名操作,这样就可以避免每个目录进行多次磁盘更新。 CONFIG_EXT2我认为这对于旧的实现或普通的文件系统不会发生ext4

ext4:这次让 fsync 同步无日志中的父目录

[...] 显然这也包括 ext2 默认模式。 [...]

https://elixir.bootlin.com/linux/v5.0.17/source/fs/ext4/fsync.c#L38

 * If we're not journaling and this is a just-created file, we have to
 * sync our parent directory (if it was freshly created) since
 * otherwise it will only be written by writeback, leaving a huge
 * window during which a crash may lose the file.  This may apply for
 * the parent directory's parent as well, and so on recursively, if
 * they are also freshly created.

和以前一样,用sync() 替换fsync() 阶段似乎提供了令人不安的良好性能,匹配--force-unsafe-io:-)。如果您可以使用它们,sync() 或syncfs() 似乎非常好。

BTFS

当我开始在 btrfs 上测试 aio_fsync() 时,我发现由于最近的数据完整性修复,fsync() 操作可能会导致文件的 rename() 阻塞。我决定我对 btrfs 不感兴趣。

为什么先调用 fsync() 时 rename() 需要更长的时间?

相关内容