SQLite 扩展失败,很多时候问题出在 SQL 之外,落在写入权威的判断偏差上。团队嘴里说着“replicated SQLite”,实际指向的常常是四种完全不同的设计:一份单点可写数据库加持续的异地恢复、一位租约意义上的单主节点加本地读副本、一套由 leader 通过 Raft 执行 SQL 的网络数据库,或者一层直接嵌进应用集群里的共识机制。这四件事无法互相替代。最快的分辨办法,是把问题问得更硬一些:两台机器一旦出现分歧,究竟由哪一层来裁定真相?[1][3][4][5][6]
SQLite 自己先把边界放在那里。它的 WAL 设计确实能让读者与单一写者高效并存,可这套机制依赖同一台主机上的共享内存,所以 SQLite 一旦要越过单机,外面就一定要再包上一层系统。[1][7] 由此往下,生态开始分叉:
- Litestream 把写入权威留在普通 SQLite 进程内部,只把 WAL 变化向外流送,用于恢复。[4][8]
- LiteFS 让每个节点都保留本地 SQLite,同时通过租约选出一位主节点,所有写入都汇到那里。[2][3]
- rqlite 把权威抬到文件之上:Raft 日志和 leader 节点成为事实来源,SQLite 负责回放这套状态机。[5]
- dqlite 也做了类似上移,只是形状更嵌入式,由 leader 线程充当网关,再由自定义 VFS 把 SQLite 写入变成 Raft 条目。[6]
顺着这条线去看,比较会比任何一张跑分图都更清楚。
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,当
- 你的主要问题是备份质量、时间点恢复,或者一台可写节点的低成本灾难恢复。[4][8]
- 你想保留普通本地 SQLite 的行为,不想在应用路径里插入一层 leader 服务。
- 你愿意把 failover 当成恢复演练,接受它与持续在线的法定多数服务分属两种路径。
优先选 LiteFS,当
- 你想要多个在线节点和本地读副本,同时又能保证任一时刻只有一位权威写者。[2][3]
- 应用确实会从“每个节点上 SQLite 都贴着进程或虚拟机存在”这件事里受益。
- 团队愿意认真持有租约纪律、primary 路由,以及 split-brain 恢复这几件事。[2][3]
优先选 rqlite,当
- 你需要一套带 leader 选举和共识提交语义的网络数据库。[5]
- 你的共享 SQL 状态重要到值得为每一次写入支付 Raft 成本,同时规模又还留在 SQLite 的舒适带里。
- 你愿意放弃直接本地文件访问,用它去交换一套更清楚的分布式数据库契约。
优先选 dqlite,当
- SQL 状态本来就属于某个集群化应用或平台组件内部,普通文件只是旁侧形态。[6]
- 嵌入式的 leader/gateway 模型,比独立恢复工具或分布式文件系统层更贴合。
- 你要的是由法定多数支撑的提交路径,同时又希望架构形状比 rqlite 那种“小型远程数据库服务”更像一块库式组件。[6]
6. 一条反证线索,与一条边界检查
好的比较,也要说清楚什么时候四种都不该选。SQLite 自己的使用建议在这里仍旧有价值:如果工作负载天然需要很多并发写者、需要一套远程 client/server 数据库,或者已经越过单机舒适带太多,那么继续拉扯 SQLite,最后就容易变成和工作负载争辩,解决问题的能力反而下降。[7] 这种场景里,更干净的答案往往是直接选择 Postgres 或其他服务端数据库,不再为这些中间层支付额外翻译成本。
另一条检查线更简单:先决定系统里的真相,究竟该落在一份文件、一位租约主节点,还是一份被复制的日志里。这个答案,通常会比功能表更早把工具指出来。
结语
SQLite 在单机之外的生态之所以健康,恰恰因为这些项目并没有在解同一个问题。Litestream 保护的是一份本地权威。LiteFS 让很多节点都保留本地文件,同时指定一位写者。rqlite 把复制日志抬到栈顶。dqlite 则把这套以日志为中心的模型更深地嵌进集群内部。[2][3][4][5][6]
若只记住一条规则,最好就是这一条:挑选那套写入权威叙事已经和你的故障模型对齐的系统。真正的架构成本,落点就在这里。
来源
- SQLite,《Write-Ahead Logging》—— WAL 的读快照、单写者行为,以及依赖同机共享内存这条边界。
- Fly Docs,《LiteFS - Distributed SQLite》—— 项目总览、生产状态说明,以及 autostop/autostart 带来过期租约回滚风险的警告。
- Fly Docs,《How LiteFS Works》—— LTX 事务文件、复制位置、租约主节点、异步复制与 split-brain 恢复机制。
- Litestream 文档,《How it works》—— 独立后台进程模型、持续 WAL 复制,以及以灾难恢复为中心的定位。
- rqlite 文档,《Design and implementation》—— 权威性的 Raft 日志、WAL 模式下的 SQLite 执行、周期性 fsync 与集群机制。
- Canonical dqlite 文档,《Replication》—— leader 网关模型、法定多数提交路径、自定义 VFS 与以 Raft 日志承担耐久性的设计。
- SQLite,《Appropriate Uses For SQLite》—— 本地 SQLite 部署与更重型 client/server 数据库之间的适配边界。
- Simon Willison,《Why I Built Litestream》—— 来自独立作者的说明,解释 Litestream 的 WAL 流式复制为何扩大了 SQLite 的可用场景。
- Wikimedia Commons,《File:Server Rack (54126210834).jpg》—— 本文题图来源页。