SQLite 是世界上部署最广泛的数据库系统,运行在数十亿设备上,从手机到卫星乃至航天器系统都可见其身影。然而,这个拥有极高可靠性的数据库,其核心开发模式却一直是「开源但不接受外部贡献」。Turso 团队在维护 libSQL fork 近两年后,做出了一个大胆的决定:从零开始用 Rust 重写 SQLite。这不仅仅是语言层面的迁移,更是一次对嵌入式数据库架构的重新思考。本文将深入解析 Turso 重写项目在存储引擎、FFI 边界与执行层的设计抉择。
从 Fork 到重写:为什么两年后选择另一条路
Turso 团队最初的选择是 fork SQLite 而非重写,这个决策在当时看来是务实的。Fork 允许他们快速获得一个可用的代码库,同时能够持续合并上游 SQLite 的新特性,避免重新实现数百个晦涩功能的巨大工程量。libSQL 在过去两年取得了显著成果:超过 12,000 颗 GitHub 星标、85 位贡献者,以及原生复制和向量搜索等创新功能。
然而,添加向量搜索功能的经历让团队意识到单纯 fork 的局限性。要实现自然流畅的 SQL 语法来查询向量数据,团队被迫在字节码生成层进行修改,而更优雅的带索引向量搜索语法在不做侵入性改动的前提下几乎无法实现。这种妥协暴露了 fork 模式的核心困境:SQLite 的测试套件是私有的,这意味着在进行大规模核心改动时很难建立足够的信心;同时,C 语言固有的不安全性让代码演进充满风险。
在评估重写的可行性时,团队联合创始人 Pekka Enberg 开展了一个实验性的个人项目,尝试估算完全重写的工作量。这个代号为「Limbo」的项目在几乎没有宣传的情况下,仅凭技术讨论就吸引了超过 1,000 颗星标和 30 多位贡献者。更重要的是,团队开始收到真正深入核心数据库层面的高质量贡献 —— 这是他们在 libSQL fork 期间一直期待却很少见到的。
存储引擎迁移:VFS 抽象层的重新设计
SQLite 的存储引擎设计围绕一个称为 VFS(Virtual File System)的抽象层展开。这个抽象将操作系统调用(文件读写、锁管理、内存映射等)从数据库核心逻辑中分离出来,使得 SQLite 可以在不同平台上运行而无需修改核心代码。Turso 的重写保留了这一设计理念,但对 VFS 层进行了重大增强。
传统 SQLite 的 VFS 实现是同步的,每个文件操作都会阻塞直到完成。这种设计在本地磁盘场景下工作良好,但当底层存储变为远程对象存储(如 S3)或浏览器中的 OPFS(Origin Private File System)时,同步模型就成为了瓶颈。Turso 的 VFS 层从一开始就被设计为异步优先,在 Linux 平台上充分利用 io_uring 接口实现真正的异步系统调用。
io_uring 是 Linux 5.1 引入的高性能 I/O 接口,它通过共享内存的提交队列和完成队列避免了传统 epoll 模型的多次上下文切换。Turso 的异步 VFS 实现将文件操作提交到 io_uring 队列后立即返回,当操作完成时通过轮询或中断通知获取结果。这种设计使得数据库可以在等待 I/O 的同时处理其他查询,大幅提升了并发场景下的吞吐量。
在浏览器环境中,Turso 的 VFS 实现直接与 OPFS 接口对接。值得注意的是,这不仅仅是简单的编译到 WebAssembly,而是整个 I/O 路径的异步化重构。SQLite 本身虽然可以编译为 WASM,但其同步 API 使得在浏览器中实现真正的高性能存储几乎不可能。Turso 的设计则将异步操作模型贯彻到底,使得在浏览器中运行数据库时可以实现与非浏览器环境相当的响应性。
FFI 边界与语言绑定的工程权衡
SQLite 的 C 语言实现使其能够轻松绑定到几乎任何编程语言,这也是它被广泛采用的重要原因之一。Turso 在决定使用 Rust 重写时必须面对一个关键问题:如何维持这种语言无关性?
团队采取了分层策略。核心数据库引擎完全使用 Rust 实现,提供完整的 SQL 方言支持和存储功能。在引擎之上,构建了一套 C FFI 接口层,这层接口的签名与 SQLite C API 保持兼容。这意味着现有的 SQLite 绑定(Python 的 sqlite3、Node.js 的 better-sqlite3、Go 的 go-sqlite3 等)理论上可以无缝切换到 Turso。
然而,实际的 FFI 边界设计比简单的接口兼容要复杂得多。SQLite 的 C API 包含大量指针传递和内存管理约定,Rust 的所有权模型虽然可以表达这些语义,但在边界处需要精心设计的 unsafe 代码。Turso 的做法是在 FFI 层使用 libc 类型直接映射 C 接口,将 Rust 端的资源管理封装在智能指针中,边界函数本身保持最小的 Rust 代码量。
另一个值得关注的工程决策是 Turso 的多语言 SDK 策略。团队不仅提供了 Rust 客户端库,还为 JavaScript/TypeScript、Go、Python 等主流语言提供了原生 SDK。这些 SDK 不是简单包装 C FFI,而是针对各语言生态进行了优化。例如,JavaScript SDK 利用 Rust 端的异步接口实现了真正的非阻塞操作,而 Go SDK 则利用 Go 的 goroutine 实现了协程级别的并发。
向量化执行与查询处理的演进
在查询执行层面,Turso 进行了向量化重构。传统 SQLite 使用行式执行模型,每次处理一条记录,这种模式在现代 CPU 上无法充分利用 SIMD 指令的并行能力。Turso 的执行引擎采用了向量化设计,运算符每次处理一批数据,批大小通常为 1024 条记录。这种批量处理不仅提高了 CPU 缓存利用率,更重要的是使得编译器能够生成更高效的向量指令。
根据团队公布的基准测试数据,在 MacBook Air M2 上执行 SELECT * FROM users LIMIT 1 查询时,SQLite 需要约 620 纳秒,而 Turso 仅需约 506 纳秒,实现了约 20% 的性能提升。这个提升幅度初看可能不够惊人,但考虑到 Turso 在很多场景下还需要处理额外的功能(如异步 I/O 的上下文切换),这个成绩已经相当可观。更重要的是,随着查询复杂度增加和数据集变大,向量化执行的优势会愈发明显。
向量搜索是 Turso 重写项目的重要驱动力之一,也是展示新架构优势的最佳案例。在 libSQL fork 中添加向量搜索时,团队被迫使用扩展方式实现,语法不够自然,需要通过显式 JOIN 访问向量索引。在 Turso 的全新设计中,向量数据类型被作为一级公民集成到类型系统中,可以与关系型数据在同一个查询中无缝混合使用。底层的向量索引实现直接利用了重写后的执行引擎,无需像 fork 版本那样在多个子系统之间艰难桥接。
可靠性保障:确定性模拟测试的深度集成
SQLite 的可靠性传奇很大程度上归功于其极其严格的测试套件,据报道测试代码量是核心代码的五倍以上。然而,这个测试套件是私有的,任何 fork 或重写项目都无法直接复用。Turso 的解决方案是构建一套全新的测试范式:确定性模拟测试(Deterministic Simulation Testing,DST)。
确定性模拟测试的核心思想是在软件内部嵌入一个模拟器,这个模拟器可以控制操作系统的各种行为,包括文件系统操作、网络延迟、进程调度等。在测试模式下,所有非确定性因素都被模拟器接管,使得测试可以在不同的执行顺序下反复运行,从而发现那些只在特定时序下才会触发的竞态条件和崩溃。
Turso 不仅自己实现了 DST 框架,还与专业测试公司 Antithesis 建立了合作关系。Antithesis 提供了一个系统级的确定性测试框架,能够模拟硬件和软件故障,包括极其罕见的部分写入场景。团队透露,Antithesis 已经帮助他们发现了 io_uring 实现中的部分写入问题 —— 这类问题在自己的 DST 框架中很难复现,因为测试环境通常会模拟完整的 I/O 操作而非真实的系统调用行为。
除了模拟测试,Turso 还采用了差异测试策略。团队编写了大量随机查询生成器,将相同的查询同时发送给 SQLite 和 Turso 执行,然后比较两者的结果和生成的字节码。这种测试方法可以有效发现执行逻辑中的细微差异,确保 Turso 在功能层面与 SQLite 保持完全兼容。
架构决策的工程权衡
Turso 的重写项目涉及大量工程权衡,以下几个决策值得深入讨论。
首先是兼容性边界的划定。团队明确表示 Turso 要在语言和文件格式层面与 SQLite 完全兼容,但这不意味着在内部架构上也要保持一致。实际上,很多 SQLite 的内部假设(如同步 I/O、 WAL 模式的实现细节)在 Turso 中被完全重写,只要最终结果一致即可。这种「黑盒兼容」策略给予架构师更大的自由度,同时为未来功能演进预留了空间。
其次是异步模型的粒度选择。SQLite 的查询执行是粗粒度的:调用 sqlite3_step 会阻塞直到返回结果或完成。Turso 将 API 扩展为支持真正的异步执行,但在 Rust 端的实现比表面看起来更复杂。异步执行需要精确的状态机来管理部分完成的查询,同时要处理 I/O 取消和超时等场景。团队选择在 Rust 的 async/await 框架基础上构建查询状态机,而非使用更轻量的线程池方案,这在内存占用和并发规模之间取得了良好平衡。
最后是功能取舍。SQLite 多年来积累了大量的可配置参数和调优选项,这在某些场景下提供了极大的灵活性,但也增加了使用复杂度。Turso 的设计哲学是提供开箱即用的最佳体验,而非让用户成为调优专家。因此,很多 SQLite 的调优选项(如 POSIX advisory locks、JOURNALMODE 等)在 Turso 中被移除或简化,团队认为这些在现代环境下已经不再重要。
面向未来的架构演进
Turso 的重写项目目前仍处于积极开发阶段,但其架构设计已经展现出明确的方向性。并发写入是下一个重要的里程碑,目前的单 writer 架构限制了高吞吐场景的扩展性。团队计划基于多版本并发控制(MVCC)实现真正的并发写入,这需要重新设计事务管理器的大部分逻辑。
Live materialized views(实时物化视图)是另一个前瞻性功能。传统物化视图需要定期刷新或手动维护,而 Turso 计划实现增量维护技术,使得视图可以随基表变化实时更新。这个功能对数据分析类应用有重要价值,其实现需要重写查询优化器的很大一部分。
从社区反馈来看,Turso 的重写策略已经产生了预期效果。项目在正式宣布后一周内星标数突破 8,000,贡献者数量翻倍至 64 人以上。更重要的是,贡献者的背景更加多元 —— 从独立开发者到大型科技公司的工程师,甚至包括正在服刑期间仍坚持贡献代码的开发者。这种社区活力是单纯 fork 很难复制的。
总结
Turso 的 Rust 重写项目代表了对嵌入式数据库架构的一次系统性重新思考。通过从零开始实现,团队摆脱了 C 语言的安全隐患,获得了异步 I/O 和向量化执行的能力,同时通过确定性模拟测试和差异测试确保了可靠性。VFS 抽象层的异步化重设计使得 Turso 能够在浏览器和边缘计算环境中发挥 SQLite 原本无法实现的价值。
这个项目也展示了开源协作的新范式。传统的「开源但不接受贡献」模式限制了 SQLite 的演进速度,而 Turso 通过完全重写的方式创建了一个真正开放的贡献平台。从 fork 到重写的路径转变,既是对技术限制的务实回应,也是对开源协作模式的深度探索。随着项目继续成熟,我们有望看到一个既保持 SQLite 极简可靠特性,又具备现代化能力的下一代嵌入式数据库。
参考资料
- Turso 官方博客:Introducing Limbo: A complete rewrite of SQLite in Rust(2024 年 12 月)
- Turso 官方博客:We will rewrite SQLite. And we are going all-in(2025 年 1 月)
- The New Stack:Why We Created Turso, a Rust-Based Rewrite of SQLite(2025 年 10 月)