Linux下如何实现可判定结果的原子文件写入与fsync同步
Linux平台WAL日志原子持久化的正确实现
你这套提交流程有几个非常典型的认知误区,很多第一次做存储引擎的开发者都会踩,先把核心问题理清楚,再讲可落地的生产级方案。
原有设计的核心问题
- 首先不存在"单字节写入天然原子"的通用保证。块设备只保证物理扇区对齐、大小不超过单扇区的写入是原子的:现在绝大多数消费级/服务器级SSD、机械盘的物理扇区是4KiB,老设备是512B,只要你的写入跨扇区、不对齐,掉电时完全可能出现半写、字节值翻转的情况,你在文件任意偏移写1字节的committed位,根本拿不到原子性保证。
- 其次你担心的"第二次fsync报错后,缓存里的committed=1会偷偷落盘"的场景,在绝大多数硬错误场景下不会发生:fsync返回EIO、ENOSPC这类硬错误时,内核会直接清掉对应脏页的标记,不会再尝试回写。只有临时错误(比如短暂的存储队列拥塞)才可能保留脏页,但这种场景下你重试fsync直到成功即可,不会出现永久的状态不确定。
- 真正的致命问题是:你把committed位放在日志记录头里,和日志内容共享同一个4KiB页,第一次fsync刷日志内容的时候,已经把committed=0的页刷到盘上了,后续改这个位本质是重写整个页,只要重写过程中掉电,整个页都可能损坏,连之前已经写好的日志内容都可能读不出来。
关于pwritev2(RWF_SYNC)的语义
带RWF_SYNC标志的pwritev2不是省一次系统调用的语法糖,它在内核里确实把"写入页缓存"和"等待对应范围IO落盘"绑定成了单次操作:调用返回成功,就保证你写入的字节已经持久化到稳定存储;返回失败,就保证没有任何对应写入落到盘上。但它能满足原子语义的前提非常苛刻:
- 写入的偏移必须严格对齐底层块设备的物理扇区大小,写入大小也必须是扇区大小的整数倍
- 你写入的committed位所在的扇区,不能和之前写的日志内容在同一个扇区里,否则还是会遇到重写扇区导致旧日志损坏的问题
- 部分老旧内核、虚拟块设备、网络文件系统不支持
RWF_SYNC的原子语义,生产环境踩坑概率很高
生产环境验证过的可行方案
不用纠结"写+fsync原子绑定"的原语,成熟的数据库、存储引擎早就绕开了这个问题,下面两种方案随便选一种,都能满足ACID里的原子性、持久性要求,而且可以明确判定每次提交的成功/失败状态。
方案1:独立对齐提交块 + 同步写
把日志内容和提交标记完全分离:
- 日志文件创建时先预分配足够的空间,避免写日志时频繁更新文件大小元数据。日志内容按顺序追加,写入时不需要带同步标志,攒够一批或者遇到提交请求时,先把所有待提交的日志内容刷到盘上,调用一次
fdatasync等内容落盘完成。 - 在日志文件头部预留一段和物理扇区大小对齐的区域(比如单独留一个4KiB块)作为提交块,每次日志内容落盘完成后,往这个提交块里写入最新的提交信息:包括已提交日志的总长度、整个提交块的校验和。
- 写提交块的时候用对齐的
pwrite,文件打开时带上O_DSYNC标志,这个写调用返回成功就代表本次提交完成,返回失败就代表提交失败,没有中间状态——因为提交块是单扇区对齐写入,块设备本身保证单扇区写要么全成要么全败,不会出现半写。 - 故障恢复时只需要读头部的提交块,校验和正确就按记录的日志长度截断文件,长度之后的半写日志直接丢弃即可。
方案2:日志记录带校验和,省去单独提交标记
这是SQLite、PostgreSQL、LevelDB等绝大多数存储引擎在用的方案,连单独的提交块写都省了:
- 每条日志记录的尾部追加整个记录的强校验和(用CRC32C或者xxHash64,不要用弱校验),写日志的时候直接按顺序追加整条记录(含校验和),写完直接调用一次
fdatasync。 - 不需要任何committed位:
fdatasync返回成功就代表这批日志提交成功;返回失败就直接把文件写指针回退到上一次已知提交成功的位置,后续覆盖写即可。 - 故障恢复时从头扫日志文件,只要某条记录的校验和不匹配,就说明读到了半写的损坏数据,直接截断到上一条校验和正确的记录位置即可,不需要额外的提交标记。
必须注意的实现细节
- 所有涉及持久化的写入,要么对齐物理扇区大小,要么靠校验和兜底,不要相信任何"小写入天然原子"的传言。
- 不要在btrfs、zfs这类CoW文件系统上直接写日志不做处理,要给日志文件设置
FS_NOCOW_FL标记,避免CoW机制导致写入顺序错乱。 - 预分配日志空间之后就不需要用
fsync,用fdatasync即可,省去刷无关元数据的开销,性能差好几倍。 - 任何同步写、
fdatasync返回错误之后,不要尝试猜测盘上的状态,直接回退到上一个有效提交点即可,校验和会帮你挡住所有不一致的状态。
内容的提问来源于stack exchange,提问作者yuri kilochek
相关产品推荐
相关产品推荐

