预写式日志(Write Ahead Log, WAL)主要用于实现存储系统中的原子性和持久性。预写式日志要求存储系统的修改操作在提交前都要先写入日志(Log)中。在硬盘数据不损坏的情况下,预写式日志允许存储系统在崩溃后能够在日志的指导下恢复到崩溃前的状态,避免数据丢失。

预写式日志并不是实现原子性和持久性的唯一办法,影页替换(Shadow Paging)是另一种实现方式,区别是预写式日志允许数据库进行原地更新(in-place),影页替换则不行。

预写式日志是一种套路,并不仅限于传统数据库中,在需要原子性和持久性的系统中经常见到它的身影。关系数据库的实现里,预写式日志常会被实现为重做(Redo Log)和撤销(Undo Log)两部分。LevelDB 中的日志模块就是预写式日志的简单应用,而最佳实践参考 ARIES algorithm。

Linux 文件系统里也有预写式日志的身影,不过叫做 Journaling。Journaling 提供了文件系统原子写入的可能,减轻存储系统实现难度。Journaling 在存储设备上开辟一段空间,用于记录文件操作,写操作先写入日志,然后复制数据到具体文件空间中,更新文件元信息,确保写入操作完成后再删除日志。Journaling 会在系统挂载文件系统时检查是否有未完成的日志,对其进行重做;系统卸载文件系统时,会将积压的日志写入。如果日志本身就不完整,直接丢弃更改。Journaling 每次操作需要将数据写两次,一种优化方式是先将数据写入对应位置,日志里只记录操作元信息,数据写入成功后再写日志。

预写式日志还应用在使用复制状态机(Replicated State Machine, RSM)进行协作的分布式系统中,如 Raft 算法为了保证安全性要求节点在将日志完整写入硬盘后才能回复该消息。

预写式日志在存储系统中扮演着举足轻重的地位,从文件系统,到分布式系统。不过在新的硬件环境下,出现了另一种与预写式日志相对的叫 Write-Behind Logging 的日志系统。WBL 在事务提交的时候,直接把藏页写入 NVRAM 中,等脏页刷盘后,再去更新日志。关于 WBL 的具体实现方式,参考论文:Write-Behind Logging