分布式 SQLite 十二年

第一个把 Raft 日志放在 SQLite 前面的系统满十二岁了。作者回顾了一个周末项目如何变成一个品类,钱又流向了谁。

中文
复制
2014 年的 rqlite 示意图:三个节点通过一朵云相连,两个是青色节点,右侧那个红色节点是负责处理写入的 leader

2014 年 9 月,我发了一篇简短的帖子,宣布 rqlite 上线。想法很简单:拿 SQLite,在它前面加一层 Raft 日志,把每次写入都复制到集群,这样丢一个节点不等于丢数据。源码当周末就上了 GitHub。据我所知,那是第一个把 Raft 和 SQLite 结合起来的开源系统。当时没有别人在做,之后好几年也没有人尝试。

2014 年的一张示意图,展示了通过云连接的三个 rqlite 节点。两个节点为青色,右侧红色的那个是负责处理写入的 leader。

今天,“分布式 SQLite”已经是一个有十几个项目的类别,其中几个拿到了风险投资,两个被收购。如今写这个类别的人往往从 2021 年讲起,所以我想把从 2014 年讲起的版本写下来。整个过程我都在场。

2014:rqlite

需求很具体。我在 jut.io 参与的一个集群系统需要少量关系型存储,而哪个节点该持有它,没有好答案。用共识协议复制 SQLite 解决了这个问题。工作中我们最终走了另一条路,但我继续做 rqlite,因为这个问题是普遍的,而解法很小。第一个版本用了 goraft,当时 InfluxDB 和 etcd 用的也是这个库(差不多同一时间,我后来以核心开发者的身份加入了 InfluxDB)。2016 年初,我把它换成了 Hashicorp 的 Raft 实现,rqlite 至今仍在使用。

最重要的设计决定是那时做出的,之后从未改变。rqlite 是一个 CP 系统。写入必须经过 leader,必须得到多数节点确认,被分区的少数派拒绝写入。HTTP API、读一致性级别、快照,其余的一切都由此而来。

2014 年之前,以及同时期的尝试

为了诚实起见,有两件事值得提。Expensify 多年来一直在自己的基础设施里用 Paxos 风格的协议复制 SQLite;他们在 2016 年将其以 Bedrock 的名字发布。还有 ActorDB,一个 Erlang 系统,让每个“actor”拥有自己的 SQLite 数据库,由 Raft 复制,大约与 rqlite 同时出现。两者都没有在起源地之外被广泛采用,但都说明这个想法当时已经在流传。rqlite 做的是把它变成一个独立的、通用的、任何人都能运行的开源东西。

2017:dqlite

Canonical 做了 dqlite,为的是让 LXD 以及后来的 MicroK8s 有一个内嵌的复制存储,而不必运行 etcd。Mark Shuttleworth 确实给我打过一次电话,我们聊过,希望 rqlite 能改造成他们需要的样子,但始终不太契合。dqlite 最初用 Go 写成,后来用 C 重写,并配上了自己的 Raft 库。在意图上,它是最接近 rqlite 的项目,主要差别在于打包方式:dqlite 是你嵌入的库,rqlite 是你运行的服务器。Canonical 需要的是产品内部的数据库,我需要的是放在任何产品旁边的数据库。

2021:Litestream,以及思路的转变

Ben Johnson(我在 InfluxDB 时曾与他共事)于2021 年初发布了 Litestream。Litestream 完全不用共识算法。它监视 SQLite 的 WAL,把内容流式传输到 S3,以几乎为零的成本为单节点 SQLite 数据库提供灾难恢复能力。Ben 的论点是:大多数应用并不需要集群,它们只用一台机器,需要的是机器挂掉之后能恢复。对相当多的应用来说,他说得没错。此后几年里“SQLite 就够了”的潮流——那些博客文章和会议演讲——很大程度上都源于这个论点。

Litestream 和 rqlite 回答的是不同的问题。Litestream:机器挂了怎么办?rqlite:机器挂了,而我等不起恢复的时间,怎么办?前一个问题更常见,后一个才是人们找到 rqlite 时真正关心的问题。

Fly.io 在 2022 年招了 Ben,并推出了 LiteFS,在文件系统层面复制 SQLite,借助 Consul 的租约实现。这是第一次尝试把复制式 SQLite 做成平台功能,而不是需要你自己部署的东西。最后一个转折是:Litestream 启发了 rqlite v8.0 处理 SQLite WAL 的方式。

2022:分支与扩展

2022 年的两个项目走了 rqlite 和 Litestream 都没走过的路。cr-sqlite 以扩展的形式给 SQLite 加上 CRDT,让多个写入方可以各自分叉、再合并,无需 leader。这是设计空间里的 AP 一角,适合 local-first 应用——在这些场景里,rqlite 的 CP 保证反而是累赘。

libSQL 则直接 fork 了 SQLite 本身。ChiselStrike(后来成为 Turso)想改动引擎内部的一些东西,而 SQLite 项目不接受外部贡献,也不会去改。在 fork 之上,他们搭了一个带边缘副本的托管服务。Turso 靠这个拿到了风险投资,2026 年被 Supabase 收购。

差不多同一时期,SQLite Cloud 出现了,提供用 Raft 复制的托管 SQLite。它是最接近 rqlite 原始设计的项目,只是包在了一层云服务里。

2022 年之后:平台

Cloudflare 于 2022 年发布 D1,把 SQLite 作为无服务器数据库放进自家的边缘网络,后来又让 Durable Objects 用上了 SQLite 存储。这是把复制版 SQLite 做成托管产品,而做这件事的公司并不需要向你解释它怎么运作。Turso 的服务也是同一个路子。这个品类已经从“你自己跑的东西”变成了“你花钱买的东西”。

十二年说明了什么

我想是三点。

核心是对的。把 SQLite 当存储引擎、在外部叠加复制,这套做法已经被至少八个团队各自重新发明过一遍,他们的需求和协议各不相同。至今没人找到停手的理由。

设计空间有三个角,而且它们按顺序出现。先是共识(rqlite、dqlite):单 leader、强一致、一个集群。然后是流式复制(Litestream):单写入者、恢复便宜、没有集群。最后是合并(cr-sqlite,以及 local-first 运动的一部分):多写入者、最终一致。每一种都对应一个不同的问题,而大多数争论,其实是各自的使用者在争论对方到底在问哪个问题。

钱则流向了平台。rqlite、dqlite 和 Litestream 是开源、免费的。拿到融资或被收购的项目,都是把复制版 SQLite 变成带账单的服务的那些。基础设施类想法通常就是这个下场,我并不介意。但值得记一笔:这个想法开源了八年,才有人把它拿去卖。

rqlite 还在。它做的还是 2014 年那件事,只是好了不少;当你想要一个容错的关系型数据库,能自己跑、能完全看懂、不用付钱给任何人,它仍然是你的选择。十二年过去,我对它在故事里的这个位置感到满意。

来源: philipotoole.com← 返回首页