【问题标题】:Does fsync/FlushFileBuffers wait for outstanding asynchronous IOs to finish?fsync/FlushFileBuffers 是否等待未完成的异步 IO 完成?
【发布时间】:2020-09-24 17:59:54
【问题描述】:

背景是开发DBMS内核,特别是数据库检查点处理。游戏规则是,我们需要等待文件上未完成的异步 IO 完成,然后再发出 fsync()。

我们部署的当前解决方案是手动计算运行中的异步 IO,等待此计数变为 0,然后再进行 fsyncing 或 FlushFileBuffer-ing。问题是我们是否真的必须这样做,也许内核/文件系统自己会这样做?

有问题的操作系统主要是 Windows 和 Linux,尽管我也很好奇基于 BSD 的操作系统如何处理这些问题。

在 Linux 上,我们使用 libaio 进行异步 IO。

【问题讨论】:

  • 你几次异步调用写文件然后FlushFileBuffers?但是你怎么能确定你打电话给FlushFileBuffers呢?拨打FlushFileBuffers时如何选择时间?所以我认为你无论如何都需要维护活动文件 I/O 的计数并在每次 I/O 完成后减少它,当计数变为 0 时 - 调用 FlushFileBuffers (但不要在专用线程中等待)。问题甚至不在FlushFileBuffers 的内部实现中,但无论如何你都需要在写完之后以某种方式调用它
  • 嗯,这就是检查点的工作原理。这是一个或多或少简单的解释xaprb.com/blog/2011/01/29/how-innodb-performs-a-checkpoint 我知道我启动了脏缓冲池页面的所有写入,直到“LSN”,例如通过 IOCP、libaio、Windows 线程池 IO。在我将这个 LSN 写入检查点记录之前,我希望这些写入是持久的,而不是正在进行的。是的,问题是关于 FlushFileBuffers 的内部实现。处理 IO 的是操作系统,所以我很好奇。
  • 你从单线程启动了所有脏页的写入,然后在它之后调用FlushFileBuffers?我最初假设可以从任意工作线程写入更复杂的情况,然后FlushFileBuffers。如果全部来自单线程顺序 - 真正的问题只在FlushFileBuffersCcFlushCache)实现
  • 你在文件上使用缓存 I/O 吗?
  • 无缓冲 IO。可能是多个线程。所有写入都是异步的,所以也许我是从一个线程还是多个线程启动它们并不重要。

标签: linux-kernel iocp windows-kernel aio fsync


【解决方案1】:

在 Windows 上:是的,对于给定的 HANDLE 实例,当前的异步 i/o 队列在 FlushFileBuffers() 执行之前被耗尽。如果你正在写一个数据库,你真的应该使用NtFlushBuffersFileEx(),它提供了更精细的同步粒度,有很大的不同。

在 FreeBSD 上:当然是 ZFS,是的。我不能说我已经测试过 UFS,但如果它不一样,我会感到惊讶。 FreeBSD 在任何情况下都将缓存的异步 i/o 实现为内核线程池,只有未缓存的异步 i/o 才是真正的异步。

在 Mac OS 上:不知道,更糟糕的是,磁盘 i/o 语义在最近几个版本中无处不在。它曾经非常好,像 BSD,但最近它已经走下坡路了。在任何情况下,异步文件 i/o 在 Mac OS 上总是几乎无法使用,最大 16 深度队列限制加上使用信号完成异步 i/o 完成的要求很难与线程代码很好地混合。

在 Linux 上:对于同步 i/o,是的 fsync() 会强制执行每个 inode 的总排序,如果您的文件系统保证(所有流行的都这样做)。对于 libaio,它在任何情况下都只适用于O_DIRECT i/o,我相信块存储层会在告诉设备屏障之前刷新所有排队的 i/o,除非你禁用了屏障。对于 io_uring(您应该使用它而不是 libaio),对于非O_DIRECT i/o,排序是文件系统对每个 i/o 强制执行的顺序一旦 io_uring 处理了提交。对于带有O_DIRECT i/o 的 io_uring,块存储层是一个单例,并且应该在整个系统中强制执行排序,一旦 io_uring 处理了提交

我一直提到“一旦 io_uring 处理了提交”,因为 io_uring 与环形缓冲队列一起工作。如果您将条目添加到提交队列,它将按提交顺序由 io_uring 处理(即队列被排空)。从提交的那一刻到 io_uring 消费提交的那一刻,没有排序。但是一旦 io_uring 消费了提交,目标文件系统就被告知了 i/o,并且无论它实现什么排序保证,它都将应用于它发送回 io_uring 的完成的排序。因此,当使用 io_uring 时,不要在 i/o 提交后继续操作,直到 io_uring 从提交队列中耗尽您的 i/o 提交请求。这很自然地发生,使用系统调用告诉 io_uring 排空队列,或者对于轮询排空,您可以观察内核在消耗提交项时自动更新的“最后一个排空项”偏移量。

来源:我是the reference library for the WG21 C++ standardisation of low level i/o的作者。警告:以上所有内容纯属我的记忆和经验,可能有点陈词滥调或错误。

【讨论】:

  • 谢谢!非常好的点。我明白,这一切都来自你的记忆,但你有没有遇到过任何关于订购的官方或半官方文件?
  • 没有官方的。有一个中间范围的 Windows 10 打破了 POSIX 读/写原子性保证,所以早期的 Win10 和所有之前的 Windows 都很好。当我向 Microsoft 报告它时,他们将其修复为向后应用的关键更新,现在每周进行一次浸泡测试以防止再次发生这种情况。因此,至少在主要平台上有很强的非官方保证。像往常一样,Linux 是所有平台中最狂野的西部。我相信 SQLite 和 Postgre 的作者也会同意这种评估。
  • @NiallDouglas 在 2.6.37 中,Linux 没有放弃对块屏障的支持吗(LWN articleKernel Newbies Changelog for 2.6.37)?
  • @MarkoMäkelä 您所指的并不是实际询问的内容。 OP 询问fsync() 是否从软件的角度强制对并发 i/o 进行排序。这与硬件实际实现的排序有关,但在任何方面都不等同。只有后者对突然断电很重要。区别与 CPU 中的原子内存操作相同,这些操作在 CPU 上创建了一个明显的代码顺序,但与 CPU 和主内存之间实际发生的事情几乎没有关系。
  • @MarkoMäkelä 当然,但同样,这不是 OP 所要求的。如果您有一堆同时发生在同一个文件上的读取和写入,可能跨越多个进程,他们会感知到撕裂的写入吗? POSIX 说不。但是fsync() 是否会阻止所有新的 i/o,直到所有现有的 i/o 耗尽并执行同步?这就是OP所要求的。这是一个纯粹的软件问题,与持久性或事务无关。这是关于订购
猜你喜欢
  • 2020-10-04
  • 2019-05-31
  • 1970-01-01
  • 2014-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-16
相关资源
最近更新 更多