Rust重写万物之——彻底重写SQLite

B站影视 2024-12-16 16:16 1

摘要:令人惊喜的是,libSQL 取得了巨大的成功。它在 GitHub 上获得了超过 12,000 个星标,拥有 85 名贡献者,并且引入了本地复制和向量搜索等先进特性,使得 libSQL 成为了 Turso 平台的核心引擎。

OSCHINA

两年前,我们对 SQLite 进行了分叉。我们非常喜欢 SQLite 的嵌入式特性,但同时我们也渴望探索一种更加开放的开发模式。

在这样的背景下,libSQL 应运而生,作为一个开放的贡献项目,我们诚挚邀请社区成员加入我们,共同构建这个项目。

令人惊喜的是,libSQL 取得了巨大的成功。它在 GitHub 上获得了超过 12,000 个星标,拥有 85 名贡献者,并且引入了本地复制和向量搜索等先进特性,使得 libSQL 成为了 Turso 平台的核心引擎。

今天,我们宣布启动一项更具雄心的实验:如果我们将 SQLite 完全用一种内存安全的语言 ——Rust 重写,将会取得怎样的成果?

通过 Limbo 项目,我们正在尝试回答这个问题。

Limbo 项目:https://github.com/tursodatabase/limbo

#01

分叉的利与弊

当我们决定分叉 SQLite 时,这并非我们唯一的选项。我们曾深入考虑过完全重写 SQLite 的可能性,但很快意识到,要使其达到生产级别的标准,需要投入巨大的时间和资源,同时保持与现有系统的兼容性也是一个挑战。分叉的优势在于,它允许我们继续从 SQLite 合并更新,从而无缝采纳新特性。

但是,分叉也有其不足之处:SQLite 的测试套件是专有的,这限制了我们对代码进行大幅度修改的信心。此外,SQLite 使用的 C 语言在内存安全方面存在局限,这使得我们在推进代码库演进时又面临其他困难。

经过深思熟虑后,我们选择了分叉这条路线,于是 libSQL 项目就诞生了。这一决策让我们得以在保持与 SQLite 的紧密联系的同时,也能够自由地进行必要的改进和创新。

#02

新的做法

将向量搜索技术整合进 SQLite 是一次突破性的尝试。我们不满足于仅仅将其作为一个附加功能,而是追求一种尽可能直观和自然的语法。为此,我们对字节码生成过程进行了精心的调整。这使我们能够将向量作为数据类型直接呈现,使得关系数据和向量数据能够在同一个表中协同工作。对于不依赖索引的查询,我们能够保持 SQL 语法的纯粹和简洁。

但是,对于那些需要索引支持的查询,除非我们愿意进行一些根本性的改动,否则很难实现我们想要的语法:

SELECT title, year FROM movies ORDER BY vector_distance_cos(embedding, vector('[4,5,6]')) LIMIT 3;

最终我们选择了以下方案:

SELECT title, year
FROM vector_top_k('movies_idx', vector('[4,5,6]'), 3)
JOIN movies
ON movies.rowid = id;

索引通常被视为一个独立的表,需要我们显式地将其与主表进行关联。

在这个阶段,我们决定探索一种创新的方法来解答一个关键问题:如果从头开始重新编写 SQLite,需要付出多少努力?我们能否轻松地维持向后兼容性?这是否能够赋予我们更多的信心,在分叉版本中大胆实施我们期望的功能,比如异步 I/O?

为了回答这些问题,Pekka 在他的个人 GitHub 账户上启动了一个充满雄心的实验项目。这个项目目前被命名为 Limbo,并且已经取得了巨大成功。尽管没有大规模的宣传,只是在 上简单说了一下,就迅速获得了超过 1,000 个 GitHub 星标,并且吸引了超过 30 位贡献者的关注和参与。

Limbo:https://github.com/penberg/limbo

#03

下一步

鉴于实验项目 Limbo 取得了巨大成功,我们决定将其正式纳入 Turso 项目体系。虽然它依然保持着实验性质,但现在已成为 Turso 的官方实验项目。这一转变将使我们能够投入更多的资源,包括公司内部其他工程师的宝贵时间和专业知识。

我们的目标是从头开始重建 SQLite,确保在语言和文件格式层面上实现完全兼容,并达到或超越 SQLite 的可靠性标准。

同时,我们致力于确保新系统在完全内存安全的基础上运行,并采用一种全新的、现代化的架构设计。

这并不意味着我们正在构建 libSQL 的竞品或替代品。实际上,如果 Limbo 项目取得成功,它将成为 libSQL 的一部分。代码将遵循与 libSQL 相同的许可证(MIT),并继续秉承我们项目一贯的社区友好态度。

#04

我们能否匹配 SQLite 闻名世界的可靠性?

由于这是一个重新实现的版本,人们可能会认为测试工作变得更加复杂。但实际情况却恰恰相反。因为我们是从零开始重新构建,我们从项目伊始就集成了确定性模拟测试(DST)。我们不仅在数据库核心中实现了 DST 功能,还与 Antithesis 合作,致力于在数据库的可靠性方面达到甚至超越 SQLite。

确定性模拟测试(DST)是一种由 TigerBeetle 团队推广的测试范式,我们在 Turso 项目中也尝试将其应用于服务器端代码。通过 DST,我们相信可以实现比 SQLite 更高的鲁棒性,因为模拟器可以更容易地模拟那些不太可能发生的情况,测试不同事件顺序下的长期执行,并且在发现问题后能够 100% 可靠地重现。

在 DST 的世界里,编写我们自己的模拟器就像是编写单元测试:它们使我们能够快速行动、轻松实验,并且彻底施压测试更改。但正如单元测试不能完全取代更高级别的集成测试一样,我们认识到我们需要付出更多努力,以达到我们期望的可靠性水平。

为了完善我们的测试策略,我们希望能够确定性地测试数据库与操作系统及其他组件交互时的行为。为此,我们与 Antithesis 合作,Antithesis 提供了一个系统级的确定性模拟测试框架,能够模拟各种硬件和软件故障。Antithesis 通过提供一个确定性的虚拟机监控器(hypervisor)来实现这一点,能够并行运行多个模糊测试线程,帮助我们快速搜索输入空间。

例如,他们已经帮助我们发现了在部分写入情况下我们的 io_uring 实现中的问题。我们的 DST 框架本身不会发现这个问题,因为在测试中,实际的 I/O 循环被替换为模拟的 I/O 循环。部分写入是一种非常罕见的情况,因此在自动化测试中很难捕捉到。

除了确定性模拟测试,我们还定期进行模糊测试输入,并确保生成的字节码在 Limbo 和 SQLite 中保持一致。

#05

当前状态

Limbo 项目虽然仍处于起步阶段,但已经取得了一些值得关注的成就。以下是几个值得注意的几个方面:

完全异步 I/O

Limbo 项目的设计宗旨是实现完全异步操作。SQLite 原生提供的是同步接口,这就意味着那些希望实现异步行为的开发者需要额外引入辅助线程。由于 SQLite 查询通常速度较快,且不涉及网络延迟,许多驱动程序开发者选择了同步接口。然而,这种做法存在两个根本性问题:

查询速度不一:并非所有 SQLite 查询都能迅速完成。例如,对大数据集进行的聚合查询往往耗时较长,哪怕数据完全存储在本地。

现代环境需求:在现代应用环境中,我们更倾向于让查询能够跨越网络执行。例如,Turso 通过 HTTP 协议提供 SQLite 服务。另一个例子是 SQLite for S3,它提供了一种无限存储空间的解决方案,数据可以被缓存到本地,但部分数据可能存储在远程服务器上。

Limbo 从设计之初就考虑到了异步操作的需求。它扩展了 SQLite 的主要 API 入口 sqlite3_step,使其能够支持异步操作,允许在数据尚未准备好被消费时将控制权返回给调用者。在 Linux 系统上,Limbo 利用了性能出色的异步系统调用 API——io_uring,以实现这一点。

专为 WASM 设计

尽管 SQLite 支持编译为 WASM 格式,这一特性更多被视为 SQLite 的附加功能。实际上,存在一些项目,比如 wa-sqlite,它们致力于扩展 SQLite 的功能,使其能够在 WASM 环境,例如 Stackblitz 中运行。Limbo 数据库从设计之初就考虑到了 WASM 构建的支持,并且已经实现了与流行工具(如 Drizzle)兼容的 VFS(虚拟文件系统)接口,无需任何额外的修改即可使用。以下是一个简单的代码示例,展示了如何在支持 WASM 的环境中使用 Limbo:

import { drizzle } from 'drizzle-orm/better-sqlite3'; import * as s from 'drizzle-orm/sqlite-core'; import { Database } from 'limbo-wasm'; const sqlite = new Database('sqlite.db'); const db = drizzle({ client: sqlite }); const users = s.sqliteTable("users", { id: s.integer, name: s.text, }) const result = db.select.from(users).all; console.log(result);

浏览器支持正在开发中。

性能

SQLite 以其卓越的性能而闻名,但在许多操作中,Limbo 已经展现出与 SQLite 相媲美甚至更优的性能。通过在 Limbo 的主目录下运行 cargo bench 基准测试,我们可以对比两者的性能:SQLite 执行 SELECT * FROM users LIMIT 1 的查询在我的 MacBook Air M2 上耗时 620 纳秒,而 Limbo 执行相同查询仅需 506 纳秒,这表明 Limbo 的性能比 SQLite 快了大约 20%。

简单性

虽然 SQLite 因其基于文件的特性而广受欢迎,易于使用,但随着时间的推移,它引入了众多调优选项,这使得用户从中获得最佳性能变得复杂且不直观。例如,在上述基准测试中,SQLite 的性能数据是在经过精细调整后得到的。为了实现性能最大化,用户需要选择 WAL(Write-Ahead Logging)模式而非传统的日志模式,并禁用 POSIX 推荐的锁机制等。

与此同时,Limbo 在保持与 SQLite 的字节码和文件格式兼容的同时,去除了我们认为对现代环境不太重要的一些特性,包括 SQLite 的 “合并” 构建系统,该系统会生成一个单一的 C 文件。通过这种简化,Limbo 提供了一个更优的即用体验,无需用户进行复杂的配置。

#06

结语

作为一个开源项目,Limbo 的成功离不开社区的广泛支持。期待未来能有更多的开发者、用户和贡献者携手合作,共同推动这个项目向前发展,使其更加成熟、稳定,并能够适应更广泛的应用场景。

本文转载自「AI大模型实验室」
原文地址:

相关来源

↓分享、在看与点赞~Orz

来源:小欢课堂

相关推荐