Lobste.rs 已迁移至 SQLite 数据库
速览
Lobste.rs 是一个技术新闻聚合与讨论网站,近日宣布将其底层数据库从 PostgreSQL 迁移至 SQLite。这一改变意味着网站将不再依赖外部数据库服务,降低了运维复杂度,但也可能面临并发写入方面的限制。该迁移在技术社区引发讨论,展示了 SQLite 在特定场景下的适用性。
AI 深度解读
背景
Lobste.rs 是一个社区驱动的技术新闻聚合网站,其代码库自 2019 年起就有一个长期未决的 issue #539,讨论将底层数据库从 MariaDB 迁移到其他方案。最初的计划是迁移到 PostgreSQL,但一直没有实质性进展。2025 年,MariaDB 被 K1 收购,这一事件重新引发了社区对数据库迁移的讨论。随后,一位名为 Rahul 的贡献者在 issue 中详细分析了 SQLite 的可行性,并提出了“Lobsters 能否跑在 SQLite 上”的疑问。最终,另一位贡献者 @thomas0 主动承接了该项目,在 2025 年 6 月正式表示愿意主导迁移工作。
核心内容
2025 年 7 月 11 日(周六),@pushcx 和 @thomas0 将 SQLite 的 Pull Request 部署到了生产环境。他们决定等到周一早高峰流量过后再发布公告,以验证 SQLite 能否应对流量峰值。结果显示,SQLite 通过了考验:CPU 使用率下降,内存使用率下降,网站响应速度更快(至少对维护者本人而言),并且一旦关闭 MariaDB 的 VPS,整体 VPS 成本降低了一半。最终,周一他们迎来了一个“安静的周一”,标志着迁移成功,issue #539 也在当天关闭。
迁移过程回顾
- 2019 年:@thomas0 偶然发现了 #539,基于自己管理、迁移大型数据库的经验,建议使用 MySQL 作为替代方案(因为 MariaDB 与 MySQL 兼容)。当时他并未打算亲自参与,因为已有讨论转向 PostgreSQL。
- 2025 年:Rahul 在 issue 中提及 K1 收购 MariaDB 的消息,引发了关于迁移到 PostgreSQL 的详细讨论。随后 Rahul 又提出了一个关于 SQLite 的详细分析贴。
- 2025 年 6 月:@thomas0 正式表示愿意接手该项目。他可能曾在 Lobsters 的“办公时间”会议中提及此事,但时间久远已记不清。
- 2025 年 8 月:@thomas0 提交了第一个 PR,但因忙碌未能及时跟进,GitHub 的 stale bot 自动关闭了该 PR。由于无法重新打开,他创建了第二个 PR。第二个 PR 包含了性能测试、一个自制的数据库 X 到数据库 Y 的迁移脚本(因为现有 MariaDB/MySQL 到 SQLite 的脚本都不满足要求),以及关于数据完整性的调试和思考。
- 2025 年 2 月 21 日(第一次部署尝试):@thomas0 和 @pushcx 一起制定了部署清单。部署过程一切顺利,直到 PR 合并后网站进入只读模式,但只读流量导致所有 CPU 飙升至 100%。他们无法定位问题,决定回滚。这次失败让 @thomas0 感到沮丧,因为他意识到无法访问生产数据库导致性能问题难以预判。
- 第一次部署失败两天后:@thomas0 提交了第三个(也是最后一个)PR。他修复了第一次部署中发现的搜索小问题,创建了一个批量数据生成脚本(花了整整一周才在本地生成一半的 Lobsters 数据集规模),并提交了三个修复性能问题的关键更改:1、2、3。性能问题的根源是 SQLite 对数据库中的最大表进行了全表扫描(共两个查询),第三个更改修复了一个 n+1 问题。在第二次部署当天早上,他还添加了慢查询日志,以防部署过程中出现更多性能问题。
- 2025 年 7 月 11 日(第二次部署):@thomas0 和 @pushcx 再次在早上通话,制定了部署和回滚清单。@pushcx 合并并部署了 PR。这次网站保持在线,CPU 和内存使用情况良好,@thomas0 松了一口气。他们监控了网站指标和 IRC,发现并迅速修复了少量问题(1、2)。总体运行正常,之后他们等待周一流量高峰。
- 周一:网站依然稳定,宣告迁移成功。
SQLite 迁移经验教训
- 自定义函数:SQLite gem 支持用户定义函数(UDF),他们利用此特性实现了 SQLite 中缺失的函数(如 regexp、if、stddev),从而避免了大量 SQL 迁移时的变通方案。
- 无符号 bigint:SQLite 不支持 unsigned bigint。MariaDB 中某些 ID 使用了 unsigned bigint,迁移时必须改为普通 bigint。
- 排序规则:SQLite 的排序规则比 MariaDB 弱。Lobste.rs 在 MariaDB 中使用了
utf8mb4_general_ci,而在 SQLite 中使用了NOCASE。但NOCASE只支持 ASCII 字符,不支持完整的 UTF 大小写折叠。 - 全文搜索表:建议使用 SQLite 的 Contentless-Delete 表(非默认选项)。@thomas0 表示对 SQLite 的默认选择感到惊讶。
Rails 迁移经验教训
- Rails 默认的 PRAGMA 设置对 Lobsters 运行良好。
- 数据库迁移是数据库相关的,需要将旧迁移文件移到一个旧迁移目录中,以便
db:migrate继续正常工作。
Lobsters 代码库经验教训
- 代码库中存在一个搜索解析器。
- 了解到了
heinous_inline_partials这个加速渲染的 hack。 - Lobsters 的测试套件至关重要,确保迁移无需大量手动测试。
总体经验教训
- 成功的关键是所有参与者之间的良好沟通。
- 在无法访问生产数据库的情况下迁移底层数据库非常困难。这是 @thomas0 第一次在无生产环境访问权限下进行底层数据库迁移,他学到的重要教训是:未来再做类似迁移时,必须确保拥有接近真实规模的数据集。
愿望
- 希望在测试中能写出“如果遇到全表扫描则失败”的断言,这样就能在第一次部署时捕获性能问题。
- 希望创建生产级数据集能比手动编写脚本并等待一周更简单。
感谢
@pushcx 对 @thomas0 的巨大贡献表示感谢,他思想周密、工作彻底,并坚韧地应对了途中出现的所有意外。同时感谢 @355E3B 在规划和运维上的帮助,以及所有在 #539 相关 issue 和 PR 中贡献的人,还有那些在只读维护期间将网站变成“派对”的 Lobsters 社区成员。他特别感激大家对于部分服务中断保持了健康的预期,甚至从中找到了乐趣。
关于“为什么不用 PostgreSQL?”
@pushcx 回应了社区中多次提出的“为什么不用 PostgreSQL?”的问题。他说明,这其实是最初的计划(在 #539 中就是),但最终选择 SQLite 有两个务实的原因:
- 自愿承担这项工作的人(@thomas0)使用的是 SQLite。
- 他不希望使用比实际需求更大、更复杂的解决方案。PostgreSQL 是他默认的项目选择,但它确实增加了运行、调优和维护一个独立服务的复杂性。
性能与资源变化
令人惊喜的是,迁移后 CPU 和内存使用率都下降了(@pushcx 原本预期 CPU 会上升、内存保持稳定)。在 #539 的末尾有一张详细图表。
数据库大小
@thomas0 早上运行了 sqlite3_analyzer 工具,并与 @dzwdz 和 @c-cube 讨论,结果链接为:
