传统的(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 重新提出了原子写入的话题(链接到视频讨论的开头)。他指出,当人们说他们想要原子写入时,有两种不同的意思:
- 硬件提供了一些原子性 API,并且这种能力通过软件堆栈以某种方式公开
- 让文件系统完成所有工作以公开某种原子写入 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 的在线指南中消失了,这表明不支持此类最终用户调整。