传统(SCSI,ATA)磁盘协议规范不能保证在突然断电的情况下每个扇区的写入都是原子操作(但请参见下文关于NVMe规范的讨论)。然而,似乎默许地认为非古老的“真实”磁盘会尽力提供这种行为(例如,Linux内核开发者
Christoph Hellwig在2017年的演示“Failure-Atomic file updates for Linux”中顺便提到了这一点)。
当涉及到合成磁盘(例如网络附加块设备、某些类型的RAID等)时,情况就不太清楚了,它们可能会或者可能不会在法律规定的规范下提供扇区原子性保证。想象一个RAID 1阵列(没有日志),由一个提供512字节大小扇区的磁盘和一个提供4KiB大小扇区的磁盘组成,从而强制RAID公开4KiB扇区大小。作为一种思想实验,你可以构建一个场景,其中每个单独的磁盘都提供了扇区原子性(相对于自己的扇区大小),但是在断电面前,RAID设备却没有提供这样的保证。这是因为它取决于RAID正在读取的是否是512字节扇区磁盘,并且在断电之前已经写入了多少个组成4KiB RAID扇区的8个512字节扇区。
有时规范只对特定的写命令提供原子性保证。SCSI磁盘规范就是一个例子,可选的WRITE ATOMIC(16)
命令甚至可以在扇区以外提供保证,但是由于是可选的,很少被实现(因此很少被使用)。更常见的COMPARE AND WRITE
命令也是原子性的(可能跨多个扇区),但对于SCSI设备来说也是可选的,并且具有与普通写入不同的语义...
有趣的是,由于Linux内核开发者Matthew Wilcox的贡献,NVMe规范被写得如此,以确保扇区原子性。符合该规范的设备必须提供扇区写入原子性的保证,并可以选择提供连续多扇区原子性,直到指定的限制(参见AWUPF
字段)。然而,如果您目前无法发送原始的NVMe命令,那么如何发现和使用任何多扇区保证尚不清楚...
Andy Rudoff是一位工程师,他谈论了他在写原子性方面所做的调查。他的演讲“保护软件免受自身影响:块写入的断电原子性”(
幻灯片)中有一个部分是
视频,在其中他谈到了断电对传统存储中正在进行的写入的影响。他描述了他如何联系硬盘制造商询问关于“
磁盘的旋转能量用于确保在断电情况下完成写入}”这一说法,但回复都不明确是否该制造商实际上执行了这样的操作。此外,没有任何制造商会说撕裂写入从不发生,而当他在Sun公司时,ZFS在测试过程中添加了块校验和,从而揭示了撕裂写入的情况。然而,并非一切都黯淡无光-Andy谈到了扇区撕裂很少见,如果写入被中断,通常只会得到旧扇区、新扇区或错误(至少损坏不是无声的)。Andy还有一份较早的{{link4:幻灯片集 写原子性和NVM驱动器设计,其中收集了流行的声明,并警告说很多软件(包括多个操作系统上的各种流行文件系统)实际上在不知情的情况下依赖于扇区写入的原子性...
(以下以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重命名是原子的,您可以使用创建新文件/ fsync /重命名操作来进行原子文件更新,从而允许应用程序自行执行双缓冲/预写式日志记录。堆栈中较低层次的另一个示例是Copy On Write文件系统,如BtrFS和ZFS。这些文件系统通过其语义为用户空间程序提供了“所有旧数据”或“所有新数据”的保证,即使磁盘可能不提供原子写入。您甚至可以将这个想法推广到磁盘本身,其中
NAND基于SSD不会覆盖现有LBA当前使用的区域,而是将数据写入新区域并保留映射以指示LBA数据的当前位置。
继续我们的简化时间线,在2015年,惠普的研究人员撰写了一篇论文《在Linux文件系统中实现应用数据的故障原子更新》(PDF)(
media),介绍了将一个新功能引入AdvFS的Linux端(AdvFS最初是DEC的Tru64的一部分):
如果使用新的O_ATOMIC
标志打开文件,则其应用数据的状态将始终反映最近成功的msync、fsync或fdatasync操作。此外,AdvFS还包括一个新的syncv
操作,将多个文件的更新合并为一个故障原子束[...]
2017年,Christoph Hellwig撰写了
实验性的XFS补丁,以提供O_ATOMIC
功能。在
"Linux中的故障原子文件更新"演讲(
幻灯片)中,他解释了自己如何从2015年的论文中获得灵感(但没有多文件支持),并且该补丁集扩展了已经存在的XFS reflink工作。然而,尽管有
最初的邮件列表帖子,但截至撰写本文时(2020年中),这个补丁集尚未进入主线内核。
在2019年Linux Plumbers Conference的数据库讨论中,MySQL开发者Dimitri Kravtchuk问是否有计划支持O_ATOMIC
(链接指向录制讨论的开始)。与会人员提到了上面的XFS工作,Intel声称他们可以在Optane上实现原子性,但Linux没有提供公开接口来使用,Google声称在GCE存储上提供16KiB的原子性1。另一个关键点是许多数据库开发人员需要比4KiB更大的原子性以避免进行双重写入-PostgreSQL需要8KiB,MySQL需要16KiB,而Oracle数据库显然需要64KiB。此外,SQLite数据库作者Dr Richard Hipp询问是否有标准接口用于请求原子性,因为目前SQLite利用F2FS文件系统通过自定义ioctl()
进行原子更新,但ioctl绑定在一个文件系统上。Chris回答说目前没有标准,也没有提供O_ATOMIC
接口。
在2021年的Linux Plumbers Conference上,Darrick Wong重新提出了原子写入的话题(链接指向讨论录像的开头)。他指出当人们说他们想要原子写入时,有两种不同的意思:
1. 硬件提供了一些原子性API,并且这种能力以某种方式通过软件堆栈暴露出来。
2. 让文件系统完成所有工作,无论硬件如何,都要暴露出某种原子写入的API。
Darrick提到Christoph过去有关于1.的想法,但是Christoph没有回到这个话题上,并且还有一些未解答的问题(如何使用户空间意识到限制,如果该功能被公开,将仅限于直接I/O,这对许多程序可能会有问题)。相反,Darrick建议解决2.的方法是提出他的
FIEXCHANGE_RANGE
ioctl,它可以交换两个文件的内容(如果在中途失败,交换可以重新开始)。这种方法没有硬件解决方案所具有的限制(例如较小的连续大小、散射聚集向量的最大数量、仅支持直接I/O),并且理论上可以在VFS中实现,从而与文件系统无关...
在2023年的Linux存储、文件系统、内存管理和BPF峰会上,讨论了一种可以被应用程序使用的暴露设备原子性的方式。在演讲之前,已经发布了一个名为“block atomic writes”的RFC补丁系列,其中包括以下内容:
- 允许查询原子写支持
- 添加了一个新的fallocate2()调用,可用于确保文件的后备对齐以进行后续的原子写操作
- 在pwritev2()中添加了一个RWF_ATOMIC标志
- 利用了SCSI WRITE ATOMIC(16) / NVMe写入
该补丁系列仅支持直接I/O和XFS。
该工作的好处如下所述:
有了这个新的界面,应用程序块将永远不会被撕裂或破碎。对于每个单独的应用程序块,在电源故障时,要么全部写入数据,要么全部不写入数据。快速的原子写入和读取意味着读取可以看到所有旧数据或所有新数据,但绝不会混合旧数据和新数据。
简而言之,如果您对整个堆栈从应用程序一直到物理磁盘都有严格控制(因此可以控制和验证整个过程),您可以安排所需的内容以利用磁盘的原子性。如果您不处于这种情况或者您正在讨论一般情况,您不应该依赖于扇区写入的原子性。
操作系统发送写入扇区到磁盘的命令时是否是原子的?
截至2020年中:
- 当使用主线4.14+的Linux内核
- 如果您正在处理真实的磁盘
一个由内核发送的扇区写入很可能是原子性的(假设扇区大小不超过4KiB)。在受控情况下(具有电池备份的控制器、声称支持原子写入的NVMe磁盘、厂商向您提供保证的SCSI磁盘等),用户空间程序可能能够使用O_DIRECT
,只要O_DIRECT
没有回退到缓冲模式,I/O操作没有在块层被拆分/合并,且您正在发送设备特定命令并绕过块层。然而,在一般情况下,无论是内核还是用户空间程序都不能安全地假设扇区写入是原子性的。
是否可能出现磁盘上的数据部分为X、部分为Y、以及部分为垃圾的情况?
从规范的角度来看,如果你正在讨论一个 SCSI 磁盘执行常规的 SCSI WRITE(16)
操作,在写入过程中发生了停电,那么答案是肯定的:一个扇区可能包含部分 X、部分 Y 和部分垃圾数据。在进行中的写入过程中崩溃意味着从正在写入的区域读取的数据是不确定的,并且磁盘可以自由选择从该区域返回哪些数据。这意味着所有旧数据、所有新数据、一些旧数据和新数据、全零、全一、随机数据等都是该扇区可能返回的"合法"值。根据 SBC-3 规范的一个旧草案:
4.9 写入失败
如果在任务集中有一个或多个执行写操作的命令正在处理时断电(例如,导致应用程序客户端发生供应商特定的命令超时)或发生介质错误或硬件错误(例如,由于错误卸载可移动介质),那些命令正在写入的逻辑块中的数据是不确定的。当被执行读取或验证操作的命令访问这些逻辑块时(例如,在通电后或在安装可移动介质后),设备服务器可能会返回旧数据、新数据或供应商特定的数据。
在读取遇到此类故障的逻辑块之前,应用程序客户端应重新发出任何未完成的执行写操作的命令。
1 2018年,Google宣布已经调整了其云SQL堆栈,并且这使得他们能够使用16k原子写入MySQL的innodb_doublewrite=0
通过O_DIRECT
... Google进行的底层定制被描述为在虚拟化存储、内核、virtio和ext4文件系统层面上。此外,一个不再可用的测试版文档标题为16 KB持久磁盘和MySQL的最佳实践(存档副本)描述了终端用户如何安全地使用该功能。更改包括:使用适当的Google提供的虚拟机,使用专门的存储,更改块设备参数,并仔细创建具有特定布局的ext4文件系统。然而,在2020年的某个时候,这个文档从GCE的在线指南中消失了,这表明不支持这样的终端用户调优。