62

澄清问题:

当操作系统发送将扇区写入磁盘的命令时,它是原子的吗?即如果在写入命令后立即断电,则新数据的写入完全成功或旧数据保持不变。我不关心在多个扇区写入中会发生什么——撕裂的页面是可以接受的。

老问题:

假设您在磁盘上有旧数据 X,您在其上写入新数据 Y,并且在写入过程中一棵树倒在电源线上。如果没有花哨的 UPS 或电池支持的磁盘控制器,您最终会得到一个撕裂的页面,其中磁盘上的数据是 X 部分和 Y 部分。您是否最终会遇到磁盘上的数据是 X 部分和 Y 部分的情况,和部分垃圾?

我一直在尝试了解数据库等 ACID 系统的设计,而在我天真的想法看来,不使用预写日志的 firebird 似乎依赖于给定的写入不会破坏旧数据(X) -仅无法完全写入新数据(Y)。这意味着如果 X 的一部分被覆盖,则只能更改 X 中被覆盖的部分,而不是我们打算保留的 X 部分。

澄清一下,这意味着如果你有一个页面大小的缓冲区,比如 4096 字节,填充了我们想要保留的一半 Y,一半 X - 我们告诉操作系统在 X 上写入该缓冲区,没有严重磁盘的情况在写入过程中我们想要保留的一半 X 损坏的失败。

4

9 回答 9

51

传统的(SCSI、ATA)磁盘协议规范不保证在突然断电的情况下任何/每个扇区写入都是原子的(但请参阅下面对 NVMe 规范的讨论)。然而,似乎默认非古老的“真实”磁盘悄悄地尽力提供这种行为(例如,Linux 内核开发人员Christoph Hellwig在 2017 年的演讲“Linux 的故障原子文件更新”中提到了这种副手)。

当涉及到合成磁盘(例如网络连接的块设备、某些类型的 RAID 等)时,事情就不太清楚了,它们可能会或可能不会提供扇区原子性保证,同时按照其给定的规范合法地表现。想象一个 RAID 1 阵列(没有日志)由一个磁盘组成,该磁盘提供 512 字节大小的扇区,但另一个磁盘提供 4KiB 大小的扇区,从而迫使 RAID 公开一个 4KiB 大小的扇区。作为一个思想实验,您可以构建一个场景,其中每个单独的磁盘都提供扇区原子性(相对于其自己的扇区大小),但 RAID 设备不会面临断电。这是因为这将取决于 512 字节扇区磁盘是否是 RAID 正在读取的磁盘,以及在电源故障之前它已写入的 4KiB RAID 扇区中有多少 8 512 字节扇区受损。

有时规范提供原子性保证,但仅限于某些写入命令。SCSI 磁盘规范就是一个例子,可选WRITE ATOMIC(16)命令甚至可以提供超出扇区的保证,但它是可选的,它很少实现(因此很少使用)。更常见的实现COMPARE AND WRITE也是原子的(也可能跨多个扇区),但它对于 SCSI 设备也是可选的,并且具有与普通写入不同的语义......

奇怪的是,感谢 Linux 内核开发人员 Matthew Wilcox , NVMe 规范的编写方式保证了扇区原子性。符合该规范的设备必须提供扇区写入原子性保证,并且可以选择提供连续多扇区原子性,直至指定限制(请参阅AWUPF字段)。但是,如果您目前无法发送原始 NVMe 命令,则尚不清楚如何发现和使用任何多扇区保证......

Andy Rudoff 是一位工程师,他谈到了他对写原子性主题所做的调查。他的演讲“从自身保护 SW:块写入的 Powerfail 原子性”(幻灯片)有一段视频,其中他谈到了电源故障如何影响传统存储的运行中写入。他描述了他是如何联系硬盘制造商关于“磁盘的旋转能量用于确保在面对断电时完成写入”的声明“但是对于该制造商是否真的执行了这样的操作,答复是不明确的。此外,没有制造商会说撕裂的写入永远不会发生,当他在 Sun 时,ZFS 向块添加了校验和,这导致他们发现了撕裂的情况在测试期间写入。不过,情况并非一片黯淡 - Andy 谈到了扇区撕裂是如何罕见的,如果写入被中断,那么您通常只会得到旧扇区,或者只有新扇区,或者错误(所以至少损坏不是沉默的). Andy 还有一个较旧的幻灯片面板 Write Atomicity 和 NVM Drive Design,它收集了流行的声明和警告,许多软件(包括多个操作系统上的各种流行文件系统)实际上在不知不觉中依赖于原子扇区写入...

(以下以 Linux 为中心,但许多概念适用于未部署在严格控制的硬件环境中的通用操作系统)

回到 2013 年,BtrFS 首席开发人员 Chris Mason 谈到了(现已不复存在的)Fusion-io 如何创建实现原子操作的存储产品(Chris 当时正在为 Fusion-io 工作)。Fusion-io 还创建了一个专有文件系统“DirectFS”(由 Chris 编写)来公开此功能。MariaDB 开发人员实现了一种模式,该模式可以通过不再进行双缓冲来利用这种行为,从而导致“每秒多 43% 的事务和一半的存储设备磨损”。Chris 提出了一个补丁,以便通用文件系统(例如 BtrFS)可以宣传它们通过新标志O_ATOMIC提供原子性保证,但也需要更改块层。说过Chris 在后来的补丁系列中也提出了块层更改,该补丁系列添加了一个功能blk_queue_set_atomic_write()。但是,这两个补丁系列都没有进入主线 Linux 内核,并且(当前 2020 年)主线 5.7 Linux 内核中也没有O_ATOMIC标志

在我们进一步讨论之前,值得注意的是,即使较低级别不提供原子性保证,只要知道写入何时达到稳定存储,较高级别仍然可以为其用户提供原子性(尽管有性能开销) . 如果 fsync()可以告诉您何时写入稳定存储(POSIX 技术上不保证但现代 Linux 上的情况)然后因为 POSIX 重命名是原子的,您可以使用 create new file/fsync/rename dance 进行原子文件更新,从而允许应用程序执行双缓冲/预写记录自己。堆栈中较低的另一个示例是 Copy On Write 文件系统,例如 BtrFS 和 ZFS。这些文件系统为用户空间程序提供了“所有旧数据”或“所有新数据”的保证,因为它们的语义在大小大于扇区的情况下崩溃,即使磁盘很多不提供原子写入。您可以将这个想法一直深入到基于 NAND 的 SSD所没有的磁盘本身LBA 的数据现在在哪里。

恢复我们的精简时间表,2015 年,惠普研究人员写了一篇关于在 AdvFS 的 Linux 端口中引入新功能的论文失败 - Linux 文件系统中的应用程序数据的原子更新 (PDF) (媒体)(AdvFS 最初是 DEC 的 Tru64 的一部分) :

如果使用新O_ATOMIC标志打开文件,则其应用程序数据的状态将始终反映最近成功的 msync、fsync 或 fdatasync。AdvFS 还包括一个新syncv操作,它将多个文件的更新组合到一个故障原子包中 [...]

2017 年,Christoph Hellwig为 XFS 编写了实验性补丁以提供O_ATOMIC. 在“Linux 的Failure-Atomic 文件更新”演讲幻灯片)中,他解释了他是如何从 2015 年的论文中获得灵感的(但没有多文件支持)以及补丁集扩展了已经存在的 XFS reflink 工作。然而,尽管有最初的邮件列表发布,但在撰写本文时(2020 年年中),此补丁集不在主线内核中。

在 2019 年 Linux Plumbers 大会的数据库跟踪期间,MySQL 开发人员Dimitri Kravtchuk 询问是否有计划支持O_ATOMIC(链接转到视频讨论的开头)。那些聚集的人提到了上面的 XFS 工作,英特尔声称他们可以在 Optane 上实现原子性,但 Linux 没有提供公开它的接口,谷歌声称在 GCE 存储1上提供 16KiB 的原子性。另一个关键点是,许多数据库开发人员需要大于 4KiB 的原子性以避免重复写入——PostgreSQL 需要 8KiB,MySQL 需要 16KiB,而 Oracle 数据库显然需要 64KiB。此外,Richard Hipp 博士(SQLite 数据库的作者)询问是否有标准接口来请求原子性,因为今天SQLite 利用 F2FS 文件系统通过 custom 进行原子更新的能力,ioctl()ioctl 绑定到一个文件系统。Chris 回答说,目前没有任何标准,也没有提供O_ATOMIC接口。

在 2021 年 Linux Plumbers 大会上,Darrick Wong 重新提出了原子写入的话题(链接到视频讨论的开头)。他指出,当人们说他们想要原子写入时,有两种不同的意思:

  1. 硬件提供了一些原子性 API,并且这种能力通过软件堆栈以某种方式公开
  2. 让文件系统完成所有工作以公开某种原子写入 API,而与硬件无关

Darrick 提到 Christoph 过去对 1. 有过想法,但 Christoph 没有回到这个话题,而且还有一些未解决的问题(如何让用户空间意识到限制,如果该功能被公开,它将被限制为直接 I/O这可能对许多程序有问题)。相反,Darrick 建议解决 2. 是提出他的FIEXCHANGE_RANGEioctl交换两个文件的内容(如果中途失败,交换是可重新启动的)。这种方法没有基于硬件的解决方案将具有并且理论上可以在 VFS 中实现的限制(例如较小的连续大小、最大数量的分散聚集向量、仅直接 I/O),因此与文件系统无关......

TLDR;如果您严格控制从应用程序到物理磁盘的整个堆栈(因此您可以控制和限定整个堆栈),您可以安排使用磁盘原子性所需的东西。如果您不在这种情况下或者您正在谈论一般情况,则不应依赖扇区写入是原子的。

当操作系统发送将扇区写入磁盘的命令时,它是原子的吗?

在撰写本文时(2020 年年中):

  • 使用主线 4.14+ Linux 内核时
  • 如果您正在处理真实磁盘

内核发送的扇区写入可能是原子的(假设扇区不大于 4KiB)。在受控情况下(电池支持的控制器、声称支持原子写入的 NVMe 磁盘、供应商向您提供保证的 SCSI 磁盘等),只要不恢复到缓冲状态,用户空间程序可以使用,I /O 没有在块层拆分/合并/您正在发送设备特定的命令并绕过块层。然而,在一般情况下,内核和用户空间程序都不能安全地假设扇区写入原子性。O_DIRECTO_DIRECT

您是否最终会遇到磁盘上的数据是 X 部分、Y 部分和垃圾部分的情况?

从规范的角度来看,如果您谈论的是执行常规 SCSI 的 SCSI 磁盘WRITE(16)并且在写入过程中发生电源故障,那么答案是肯定的:一个扇区可能包含部分 X、部分 Y 和部分垃圾。飞行中写入期间的崩溃意味着从正在写入的区域读取的数据是不确定的,并且磁盘可以自由选择它作为来自该区域的数据返回的内容。这意味着所有旧数据、所有新数据、一些新旧数据、全零、全一、随机数据等都是为所述扇区返回的“合法”值。来自SBC-3 规范的旧草案

4.9 写入失败

如果一个或多个执行写操作的命令在任务集中并且在断电(例如,导致应用程序客户端的供应商特定命令超时)或发生介质错误或硬件错误(例如,因为可移动介质未正确卸载),这些命令正在写入的逻辑块中的数据是不确定的。当被执行读取或验证操作的命令访问时(例如,在通电之后或在安装可移动介质之后),设备服务器可以在这些逻辑块中返回旧数据、新数据或供应商特定数据。

在读取遇到此类故障的逻辑块之前,应用程序客户端应重新发出执行未完成的写操作的任何命令。


1 2018 年,谷歌宣布已调整其云 SQL 堆栈,这允许他们使用 16k 原子写入 MySQL 和innodb_doublewrite=0via O_DIRECT...谷歌执行的底层定制被描述为虚拟化存储、内核、virtio 和 ext4 文件系统层. 此外,不再提供名为16 KB 永久磁盘和 MySQL 的最佳实践的测试版文档(存档副本)描述了最终用户必须做什么才能安全地使用该功能。更改包括:使用适当的 Google 提供的 VM、使用专门的存储、更改块设备参数以及仔细创建具有特定布局的 ext4 文件系统。但是,在 2020 年的某个时候,该文档从 GCE 的在线指南中消失了,这表明不支持此类最终用户调整。

于 2020-05-16T06:37:07.643 回答
21

我认为撕裂的页面不是问题。据我所知,所有驱动器都存储了足够的电量,可以在断电时完成当前扇区的写入。

问题是每个人都在撒谎。

至少当数据库知道事务何时提交到磁盘时,每个人都在撒谎。数据库发出 fsync,操作系统只有在所有未完成的写入都提交到磁盘时才返回,对吗?也许不吧。这是很常见的,特别是对于 RAID 卡和/或 SATA 驱动器,您的程序被告知一切都已提交(即 fsync 返回),但驱动器上还没有数据。

您可以尝试使用Brad 的磁盘检查器来确定您要用于数据库的平台是否可以在拔出插头后不丢失数据。底线:如果 diskchecker 失败,则该平台对于运行数据库是不安全的。具有 ACID 的数据库依赖于知道事务何时已提交到后备存储以及何时未提交。无论数据库是否使用预写登录都是如此(并且如果数据库在没有进行 fsync 的情况下返回给用户,那么在发生故障时事务可能会丢失,因此不应声称它提供了 ACID 语义)。

Postgresql邮件列表上有一个很长的帖子讨论持久性。它开始谈论 SSD,但随后涉及 SATA 驱动器、SCSI 驱动器和文件系统。您可能会惊讶地发现您的数据有多么容易丢失。对于任何需要持久性的数据库的人来说,这都是一个很好的线程,而不仅仅是那些运行 Postgresql 的人。

于 2010-01-05T22:15:49.980 回答
17

似乎没有人同意这个问题。所以我花了很多时间尝试不同的谷歌查询,直到最终找到答案。

来自 RedHat 员工、Linux 内核文件系统和虚拟内存开发人员 Stephen Tweedie 博士在此处关于 ext3(他开发的)脚本的谈话。如果有人知道,那就是他。

“仅仅将内容写入日志是不够的,因为日志中必须有一些标记,上面写着:嗯,(实际上有这个日志记录)这个日志记录真的代表了与磁盘的完全一致性吗?而且你这样做的方法是进行一些原子操作,将事务标记为在磁盘上完成" [23m, 14s]

“现在,现在的磁盘实际上做出了这些保证。如果您开始对磁盘进行写入操作,那么即使在该扇区写入过程中电源发生故障,磁盘也有足够的可用电源,它实际上可以从磁盘中窃取电源。 “ [23m,41s]

于 2010-01-15T00:29:48.013 回答
10

不,他们不是。更糟糕的是,在默认设置下,磁盘可能会谎称数据实际上已在磁盘缓存中写入。出于性能原因,这可能是可取的(实际的持久性要慢一个数量级),但这意味着如果您断电并且没有物理写入磁盘缓存,您的数据就会消失。

不幸的是,真正的持久性既困难缓慢,因为每次写入至少需要进行一次完整旋转,或者使用日志/撤消进行 2 次以上。这将您限制为每秒几百个数据库事务,并且需要在相当低的级别禁用写入缓存。

但是,出于实际目的,在大多数情况下,差异并没有那么大。

看:

于 2010-01-14T05:21:22.827 回答
8

如果电源出现故障,人们似乎不同意在扇区写入期间会发生什么。也许是因为它取决于所使用的硬件,甚至是文件系统。

来自维基百科(http://en.wikipedia.org/wiki/Journaling_file_system):

一些磁盘驱动器在电源故障期间保证写入原子性。然而,其他人可能会在断电后中途停止写入扇区,从而使其与其纠错码不匹配。该扇区因此损坏并且其内容丢失。物理日志可以防止这种损坏,因为它拥有扇区的完整副本,它可以在下一次装载时重放损坏。

似乎表明某些硬盘驱动器不会完成写入扇区,但日志文件系统可以保护您免受数据丢失,就像 xlog 保护数据库一样。

来自关于 ext3 日志文件系统的讨论中的 linux 内核邮件列表:

在任何情况下,坏扇区校验和都是硬件错误。扇区写入应该是原子的,它要么发生,要么不发生。

我倾向于相信维基评论。实际上,没有 xlog 的数据库(firebird)的存在意味着扇区写入是原子的,它无法破坏您不想更改的数据。

这里有很多关于扇区写入原子性的讨论,但再次没有达成一致。但是不同意的人似乎在谈论多扇区写入(在许多现代硬盘驱动器上不是原子的)。那些说扇区写入是原子的人似乎确实更了解他们在说什么。

于 2010-01-06T17:58:11.947 回答
5

第一个问题的答案取决于所涉及的硬件。至少对于一些较旧的硬件,答案是肯定的——电源故障可能会导致垃圾被写入磁盘。然而,大多数当前的磁盘都在磁盘本身内置了一些“UPS”——一个足够大的电容器,可以为磁盘供电足够长的时间,以便将磁盘缓存中的数据写入磁盘盘片。它们还具有检测电源是否仍然良好的电路,因此当电源不稳定时,它们会将缓存中的数据写入盘片,而忽略它们可能收到的垃圾。

就“撕裂页面”而言,典型的磁盘一次只接受写入整个扇区的命令,因此您通常会得到正确写入的整数个扇区,而其他扇区保持不变。但是,如果您使用的逻辑页面大小大于单个扇区,那么您肯定会得到一个部分写入的页面。

然而,这主要适用于直接连接到普通的移动盘片式硬盘驱动器。对于几乎任何其他事情,规则可以而且经常会有所不同。举一个明显的例子,如果你在网络上写,你主要受所使用的网络协议的支配。如果您通过 TCP 传输数据,与 CRC 不匹配的数据将被拒绝,但可能会接受通过 UDP 传输的具有相同损坏的相同数据。

于 2010-01-05T21:17:52.917 回答
2

我怀疑这个假设是错误的。

现代 HDD 对扇区中的数据进行编码 - 并使用 ECC 对其进行额外保护。因此,您最终可能会垃圾所有扇区内容 - 使用的编码将没有意义。

至于越来越受欢迎的 SSD,情况就更可怕了——在被覆盖之前块被清除,因此,根据所使用的固件和可用空间的大小,完全不相关的扇区可能会被损坏。

顺便说一句,操作系统崩溃不会导致单个扇区内的数据损坏。

于 2010-01-05T21:09:01.670 回答
0

我希望一个撕裂的页面由 X 部分、Y 部分和部分不可读的扇区组成。如果电源故障时磁头正在写入扇区,则驱动器应立即停放磁头,以使驱动器的其余部分(除该扇区之外)保持完好无损。

在某些情况下,我希望有几张由 X 部分和 Y 部分组成的残页,但只有一个残页会包含不可读的扇区。多个页面撕裂的原因是驱动器可以在内部缓冲大量写入,并且写入顺序可能会在不同页面中交错不同扇区。

我读过关于对不可读扇区的新写入是否会使其再次可读的相互矛盾的故事。即使答案是肯定的,那将是新数据 Z,既不是 X 也不是 Y。

于 2010-01-14T03:57:21.540 回答
-1

更新磁盘时,驱动器制造商的唯一保证是单个 512 字节写入是原子的(即,它要么全部完成,要么根本不完成);因此,如果发生不合时宜的断电,则可能仅完成较大写入的一部分(有时称为撕裂写入)。

于 2017-06-04T04:26:00.170 回答