SQLite 扩展失败,很多时候问题出在 SQL 之外,落在写入权威的判断偏差上。团队嘴里说着“replicated SQLite”,实际指向的常常是四种完全不同的设计:一份单点可写数据库加持续的异地恢复、一位租约意义上的单主节点加本地读副本、一套由 leader 通过 Raft 执行 SQL 的网络数据库,或者一层直接嵌进应用集群里的共识机制。这四件事无法互相替代。最快的分辨办法,是把问题问得更硬一些:两台机器一旦出现分歧,究竟由哪一层来裁定真相?[1][3][4][5][6]

SQLite 自己先把边界放在那里。它的 WAL 设计确实能让读者与单一写者高效并存,可这套机制依赖同一台主机上的共享内存,所以 SQLite 一旦要越过单机,外面就一定要再包上一层系统。[1][7] 由此往下,生态开始分叉:

顺着这条线去看,比较会比任何一张跑分图都更清楚。

1. Litestream:把文件留在本地,把恢复做成连续动作

Litestream 是最窄的一条延展路径,同时对很多团队来说,也最诚实。它自己的文档写得很清楚:这是一个独立运行的后台进程,会持续把 WAL 页面从磁盘复制到一个或多个副本位置。[4] 应用仍旧面对一份普通的本地 SQLite 文件,写路径没有挪进集群协调器,也没有挪到远端 leader 里。权威仍旧留在 SQLite 原本最习惯的地方:正在运行的那个进程,以及那一份可写数据库文件。[1][4]

也正因为如此,Litestream 更适合被理解成一条灾难恢复路径,自动故障切换超出了它的核心承诺。它大幅改善了备份质量,因为 WAL 流意味着时间点恢复,能力范围超过一批间隔性的冷快照。[4][8] 它没有抹掉单主现实。宿主机一旦消失,后续动作是从复制状态里恢复或提升,现场并没有一套依靠法定多数继续写入的在线服务。

这条承诺收得很窄,优点也正落在这里。单区域应用、内部工具、只有一个写节点的小型 SaaS,或者主要需要廉价耐久性的控制面组件,都能从这里拿到不少收益,同时继续保留本地 SQLite 的语义。真正需要自动 leader 选举、多个活动节点上的本地读取,或一套远程客户端协议时,需求已经越过 Litestream 的边界了。[4][7][8]

2. LiteFS:让 SQLite 到处都在本地,同时只指定一位写者

LiteFS 走了另一条路。Fly 的文档把它定义成一套专门为复制 SQLite 数据库设计的分布式文件系统,因此每个应用节点都可以持有数据库的完整本地副本,并以较低延迟回应请求。[2][3] 这听起来像是“分布式 SQLite”,真正更重要的中心却更具体。因为 SQLite 仍旧是一套单写者系统,LiteFS 用租约机制指定某一个节点在任一时刻充当 primary,所有写入都被导向这一点。[3]

这条决定,直接塑造了 LiteFS 的实际形状。读操作可以贴着应用分布在多个节点上,因为每个节点手里都有本地副本;写操作则被收束到 primary。底层上,LiteFS 会拦截文件系统调用,把页变化整理成自己的 LTX 事务文件,再用事务 ID 与 rolling checksum 跟踪复制位置,并以异步方式把变更送到副本节点。[3] 一旦出现 split-brain 风险,也正是这套 rolling checksum 告诉 LiteFS,落后节点应该重新做 snapshot,不能把自己的本地状态继续当作可信事实。[3]

这笔交换适合这样的应用:既想保留 SQLite 的文件语义和多机上的本地读性能,又能严格执行清晰的单写者纪律。周边平台如果过于频繁地重启或迁移节点,这套设计就会变得紧张。Fly 的总览页已经直接警告,不要把 LiteFS 和 Fly Machines 的 autostop/autostart 混在一起,因为过期机器会重新赢得租约,并丢弃较新的复制状态。[2] 这条警告本身就把要点说完了:LiteFS 仍是一套被精细管理的单主系统,副本很快,读也很近,却没有提供神奇的多主 SQL。

3. rqlite:Raft 日志才是数据库契约

rqlite 是“把权威彻底移到 SQLite 文件之上”最清楚的一种做法。它的设计文档写明,Raft 日志按执行顺序保存已经提交的 SQLite 命令,而且这份日志就是系统中一切变化的权威记录。[5] 集群里的每一个节点都按同样的方式应用这份日志,于是 SQLite 数据库在每台机器上保持一致。[5] 一旦接受这种模型,rqlite 看起来就不再像“稍微更高可用一点的 SQLite”,而更像一套规模收得很小的分布式数据库,只是它碰巧用 SQLite 作为执行引擎。

客户端模型也因此一并改变。应用不再直接打开本地 .db 文件,让复制在背后发生。它要去和一套网络服务对话。rqlite 暴露 HTTP API,自己处理 leader 选举与集群组织,并且在写路径上主动支付 Raft 的成本。[5] 落盘文件仍旧重要,它承担运行时与恢复时的角色,系统里的最高权威已经移交给那份被复制出来的日志。

这条路径适合那些明确需要共识语义、同时愿意接受 SQLite 已经离开应用进程旁边本地状态位置的团队。控制面、元数据存储、作业调度器、特征服务,或者规模适中但必须共享的关系状态,都能在这里读出很好的契合感。反过来看,若一个团队真正珍视 SQLite 的理由,是应用和数据库共处一台机器、共享同一故障域,那么 rqlite 其实已经有意识地越过了这条线。[1][5][7]

4. dqlite:把共识数据库嵌进集群内部

dqlite 也会把 SQLite 写入转成 Raft 支撑的集群状态,只是它的形状更嵌入式。Canonical 的复制文档写得很清楚:客户端必须连到当前 leader 线程,这个 leader 充当网络客户端与 SQLite 之间的网关。[6] 读事务可以直接服务;写事务则要先把数据库变化编码成一条 Raft 日志,再等到法定多数节点接收并持久化这些条目之后,才向客户端确认提交。[6]

更能说明问题的是它的存储模型。dqlite 会让 SQLite 使用一套自定义 VFS,把文件映像放在常规进程内存里,再把持久性责任交给 Raft 日志,普通 SQLite 数据库文件退到后面。[6] 在 WAL 模式下,SQLite 仍旧通过 WAL frame 与 commit marker 表达事务变化,只是 dqlite 会在自己的 VFS 和复制层内部承接这些动作。[6] 因而,dqlite 的感觉已经越过“把数据库跑在应用旁边”,更接近“把一块由共识托住的 SQL 组件建进集群本体内部”。

这条路径在数据库本来就是应用平台一部分、而且更适合做 leader-aware 嵌入时,会显得很强。若团队更看重单二进制的运维可读性,或者更想守住一份朴素的本地 SQLite 文件,它的贴合度就会下降。也正是在这个层面上,dqlite 的力量来自于它没有像 Litestream 那样隐身,也没有像 LiteFS 那样继续把文件留在中心位置。

5. 按故障模型来选

如果只够开一次架构讨论,最好先问清楚:你究竟在为哪一种故障付费。

优先选 Litestream,当

优先选 LiteFS,当

优先选 rqlite,当

优先选 dqlite,当

6. 一条反证线索,与一条边界检查

好的比较,也要说清楚什么时候四种都不该选。SQLite 自己的使用建议在这里仍旧有价值:如果工作负载天然需要很多并发写者、需要一套远程 client/server 数据库,或者已经越过单机舒适带太多,那么继续拉扯 SQLite,最后就容易变成和工作负载争辩,解决问题的能力反而下降。[7] 这种场景里,更干净的答案往往是直接选择 Postgres 或其他服务端数据库,不再为这些中间层支付额外翻译成本。

另一条检查线更简单:先决定系统里的真相,究竟该落在一份文件、一位租约主节点,还是一份被复制的日志里。这个答案,通常会比功能表更早把工具指出来。

结语

SQLite 在单机之外的生态之所以健康,恰恰因为这些项目并没有在解同一个问题。Litestream 保护的是一份本地权威。LiteFS 让很多节点都保留本地文件,同时指定一位写者。rqlite 把复制日志抬到栈顶。dqlite 则把这套以日志为中心的模型更深地嵌进集群内部。[2][3][4][5][6]

若只记住一条规则,最好就是这一条:挑选那套写入权威叙事已经和你的故障模型对齐的系统。真正的架构成本,落点就在这里。

来源

  1. SQLite,《Write-Ahead Logging》—— WAL 的读快照、单写者行为,以及依赖同机共享内存这条边界。
  2. Fly Docs,《LiteFS - Distributed SQLite》—— 项目总览、生产状态说明,以及 autostop/autostart 带来过期租约回滚风险的警告。
  3. Fly Docs,《How LiteFS Works》—— LTX 事务文件、复制位置、租约主节点、异步复制与 split-brain 恢复机制。
  4. Litestream 文档,《How it works》—— 独立后台进程模型、持续 WAL 复制,以及以灾难恢复为中心的定位。
  5. rqlite 文档,《Design and implementation》—— 权威性的 Raft 日志、WAL 模式下的 SQLite 执行、周期性 fsync 与集群机制。
  6. Canonical dqlite 文档,《Replication》—— leader 网关模型、法定多数提交路径、自定义 VFS 与以 Raft 日志承担耐久性的设计。
  7. SQLite,《Appropriate Uses For SQLite》—— 本地 SQLite 部署与更重型 client/server 数据库之间的适配边界。
  8. Simon Willison,《Why I Built Litestream》—— 来自独立作者的说明,解释 Litestream 的 WAL 流式复制为何扩大了 SQLite 的可用场景。
  9. Wikimedia Commons,《File:Server Rack (54126210834).jpg》—— 本文题图来源页。