一个开源数据库项目,居然要把SQLite内置的日志机制完全接管过来。rqlite 10.0版本最近发布,核心改动就藏在这个看似反直觉的设计里。

rqlite的定位很明确:轻量级、开源、具备容错能力的关系型数据库,底层用SQLite存储,用Raft协议做分布式一致性。但SQLite原生的WAL(预写日志)工作方式,和rqlite的需求存在根本冲突。

打开网易新闻 查看精彩图片

SQLite自己管理WAL时,会在日志膨胀到一定程度自动checkpoint,或者在最后一个连接关闭时触发。这些时机对单机数据库没问题,但对rqlite是灾难——因为rqlite把WAL当成了增量状态追踪的核心载体,一旦SQLite擅自checkpoint,这份"增量快照"就失效了。

问题的根源在Raft的设计。Raft只干一件事:维护一份状态机变更日志,并确保它在集群各节点完全一致。为了防止日志无限增长,Raft需要定期做snapshot:把当前状态机完整拷贝到磁盘,然后删除已被snapshot覆盖的日志条目。

rqlite早期用的就是HashiCorp Raft库默认的snapshot方案:Raft要snapshot时,直接拷贝整个SQLite数据库文件。2GB的数据库,哪怕只改了几百行,也要完整复制2GB。部署规模上到多GB级别后,这个方案彻底跑不动了。

打开网易新闻 查看精彩图片

WAL模式给了rqlite出路。SQLite在WAL模式下,所有修改先写成"帧"(frame),每帧对应一个被改动的数据页。checkpoint之前,WAL里精确保存着自上次checkpoint以来的全部变更。

rqlite的新方案很直接:Raft请求snapshot时,rqlite先复制当前WAL交给Raft,然后自己触发checkpoint把WAL刷回主数据库。WAL清空,重新开始累积下一批变更。任何时候,WAL内容就是"相对于最后一次Raft snapshot的未提交状态"。

这样snapshot的代价从"全量拷贝数据库"降到"拷贝WAL文件",通常只有几十MB甚至更小。代价是rqlite必须完全掌控checkpoint时机——SQLite不能再自作主张。