使用内存映射文件或普通 Stream.Write 时是否有任何持久性保证

Are there any persistence guarantees when using memory mapped files or plain Stream.Write

我有很多数据想以二进制形式保存到磁盘,我想尽可能接近具有 ACID 属性。由于我有很多数据,无法将它们全部保存在内存中,我知道我有两种基本方法:

  1. 有很多小文件(例如,每分钟左右写入磁盘)- 万一发生崩溃,我只会丢失最后一个文件。但是,性能会更差。
  2. 有一个大文件(例如打开、修改、关闭)- 之后的最佳顺序读取性能,但如果发生崩溃,我可能会得到一个损坏的文件。

所以我的问题具体是:

如果我选择大文件选项并将其作为内存映射文件打开(或使用 Stream.PositionStream.Write),并且出现断电,是否有任何保证文件可能发生什么?

  1. 是否可能丢失整个大文件,或者只是数据在中间损坏?

  2. NTFS 是否确保一定大小(4k?)的块总是被完全写入?

  3. better/worse的结果是Unix/ext4吗?

我想避免使用 NTFS TxF,因为 Microsoft 已经提到它计划停用它。我正在使用 C#,但语言可能无关紧要。

(补充说明)

似乎应该有一定的保证,因为——除非我错了——如果在写入文件时可能会丢失整个文件(或遭受非常奇怪的损坏),那么现有的数据库都不会是 ACID,除非他们 1) 使用 TxF 或 2) 在写入之前复制整个文件?如果您丢失了您甚至不打算触摸的部分文件,我认为日记不会帮助您。

NTFS 文件系统(和 ext3-4)使用事务日志来操作更改。每个更改都存储在日志中,然后,日志本身用于有效地执行更改。 除了灾难性的磁盘故障,文件系统被设计为在 它自己的 数据结构中保持一致,而不是你的:如果发生崩溃,恢复过程将决定按顺序回滚什么以保持一致性。如果回滚,您的 "not-yet-written but to-be-written" 数据将丢失。 文件系统将是一致的,而您的数据不是。

此外,还涉及其他几个因素:软件和硬件缓存引入了一个额外的层,因此会出现故障点。通常操作在缓存中执行,然后缓存本身被刷新到磁盘上。文件系统驱动程序不会看到执行的操作 "in" 缓存,但我们会看到刷新操作。 这样做是出于性能原因,因为硬盘驱动器是瓶颈。硬件控制器确实有电池来保证即使在断电的情况下也可以刷新它们自己的缓存。

扇区的大小是另一个重要因素,但不应考虑此细节,因为出于互操作性目的,硬盘驱动器本身可能会谎报其原始大小。

如果您映射了内存并在中间插入数据,当断电时,如果文件内容超过内部缓冲区的大小,则文件内容可能会部分包含您所做的更改。

TxF 是一种缓解该问题的方法,但有几个含义限制了您可以使用它的上下文:例如,它不适用于不同的驱动器或共享网络。

为了成为 ACID,您需要按照您的使用方式设计数据结构 and/or,以便不依赖于实现细节。例如,Mercurial(版本控制工具)总是将自己的数据附加到自己的修订日志中。 有许多可能的模式,但是,您需要的保证越多,您将获得(并绑定到)的技术越具体。

您可以调用 FlushViewOfFile,启动脏页写入,然后调用 FlushFileBuffersaccording to this article,保证页面已写入。

每次写入后调用 FlushFileBuffers 可能 "safer" 但不推荐这样做。你必须知道你能承受多少损失。有一些模式可以限制这种潜在的损失,即使是最好的数据库也可能会出现写入失败。您只需要以尽可能少的损失恢复生机,这通常需要通过多阶段提交进行一些日志记录。

我想可以使用 FILE_FLAG_NO_BUFFERINGFILE_FLAG_WRITE_THROUGH 打开内存映射文件,但这会耗尽您的吞吐量。我不这样做。我打开异步 I/O 的内存映射文件,让 OS 使用它自己的异步 I/O 完成端口实现来优化吞吐量。这是最快的吞吐量。我可以容忍潜在的损失,并适当地减轻了损失。我的内存映射数据是文件备份数据...如果我检测到丢失,一旦硬件错误被清除,我可以检测并重新备份丢失的数据。

显然,文件系统必须足够可靠才能运行数据库应用程序,但我不知道有任何供应商建议您仍然不需要备份。坏事 发生。计划损失。我做的一件事是我 从不 写入数据的中间。我的数据是不可变的和版本化的,每个 "data" 文件限制为 2gb,但每个应用程序采用不同的策略。