Lobste.rs 从 PostgreSQL 迁移到 SQLite:一个链接聚合站的数据库抉择

Lobste.rs 从 PostgreSQL 迁移到 SQLite:一个链接聚合站的数据库抉择

SQLitePostgreSQLLobsters数据库站点架构迁移

数据源:Lobsters + web research · Lobsters

2026 年 7 月 11 日(周六),技术链接聚合站 Lobste.rs 完成了一次不同寻常的数据库迁移:将生产环境从 MariaDB 切换到了 SQLite。

Lobste.rs

两天后(周一),贡献者 thomas0 在站内发帖宣布迁移成功,并分享了近一年的曲折历程。截至本文写作时,该帖获得 280 个点赞和 43 条评论。

一次穿越七年的数据库争论

故事的起点比很多人想象的要早。2018 年 8 月,Lobste.rs 的主要维护者 pushcx 在 GitHub 上开了 issue #539,标题是「讨论迁移到 PostgreSQL」。当时社区的讨论方向很明确:MariaDB → PostgreSQL。有人提到了 CTE 支持更好、全文搜索可能更优等好处,也有人指出迁移工作量大、视图兼容性需要处理。

这个 issue 在接下来的几年里缓慢发展。2019 年,thomas0 第一次参与讨论,建议考虑 MySQL(因为与 MariaDB 的兼容性更好),但当时并没有深入参与。真正的转折出现在 2025 年——社区成员 Rahul 指出了 K1 投资集团对 MariaDB 的收购,这一事件让团队重新审视了 MariaDB 的长期前景。紧接着,Rahul 又抛出一个大胆的问题:「Lobsters 能在 SQLite 上跑吗?」

从 PostgreSQL 到 SQLite,这个方向转变本身就是一个有趣的技术故事。

从第一次失败到第二次成功

thomas0 在 2025 年 6 月正式接手这个迁移项目。他在帖子里详细记录了三次 PR 尝试:

  • 第一次 PR(2025 年 8 月):因为个人时间安排被 GitHub 的 stale bot 自动关闭,且无法重新打开。
  • 第二次 PR:包含了性能测试、自研的数据库迁移脚本(市面上的 MariaDB/MySQL → SQLite 工具未能满足需求)、数据完整性验证等。
  • 第三次 PR(最终版):在前两次基础上修复了搜索问题,新增了批量数据生成脚本用于本地性能测试,并解决了第一次部署中暴露的三个性能瓶颈。

第一次部署尝试发生在 2026 年 2 月 21 日。pushcx 和 thomas0 一起制定了部署清单,一切顺利直到 PR 合并上线——站点进入只读模式后,所有 CPU 瞬间飙到 100%。无法在短时间内定位问题,团队选择回滚。

事后分析发现,问题出在三个层面:(1) SQLite 对最大的表执行了全表扫描;(2) 另一条查询也存在类似的扫描问题;(3) 代码中存在一个 N+1 查询。thomas0 坦言,由于没有生产数据库的访问权限,他事前就知道性能可能是个隐患,但第一次部署的失败仍然让他「感觉不太好」。

第二次部署与迁移结果

7 月 11 日的第二次部署吸取了之前的教训。团队制定了详细的部署和回滚清单,thomas0 还新增了慢查询日志作为保险。部署完成后站点保持正常运行,CPU 和内存使用率都保持在健康水平。他们通过 IRC 和站点指标监控用户反馈,及时修复了几个小问题,然后等待周一流量高峰的考验。

周一平稳度过。最终成果:

  • CPU 使用率下降
  • 内存使用率下降
  • 站点响应速度提升(至少 thomas0 自己感觉如此)
  • VPS 成本将减半(等 MariaDB 服务器下线后)

社区讨论中的关注点

评论区的讨论集中在几个方向上。

关于 SQLite 默认配置。 多位用户指出 SQLite 的默认设置偏保守,例如 WAL 模式、外键约束和外键检查都不是默认开启的。社区成员 zipy124 解释,这是为了保持严格的向后兼容性,默认值实际上被「冻结」了。kwas 补充说,某些默认配置更适合传统 HDD 而非现代 NVMe 存储。

关于并发模型。 用户 akavel 提出了一个常见疑问:SQLite 的并发能力是否足以支撑生产 Web 应用?多人的回复勾勒出基本图景:SQLite 在 WAL 模式下支持多读单写,通过文件系统锁来协调。对于 Lobste.rs 这种读多写少的链接聚合站,这个模型足够使用。thomas0 确认 Lobste.rs 运行在单台 VPS 上,使用 Rails 默认的 WAL journal 模式,由 Hatchbox 处理零停机部署。

关于备份策略。 用户 sumner 询问是否使用了 Litestream(SQLite 的实时复制工具),thomas0 回复使用的是 nightly restic 全量备份。bakkot 追问为什么不用 sqlite3_rsync 做增量同步,kevincox 分享了自己的负面经验——sqlite3_rsync 曾多次导致目标数据库损坏,不建议在生产环境使用。xavdid 则关心日备份窗口内可能丢失数据的风险。

关于搜索功能。 用户 yawaramin 发现迁移后搜索部分关键词时结果不太理想,gecko 解释这可能与「故事文本被索引」而非仅索引标题有关,属于站点原有的搜索行为。thomas0 也提到 SQLite 的排序算法可能与 MariaDB 不同,这可能导致搜索结果排序有细微变化。

迁移中的技术细节

thomas0 在帖子的「SQLite 经验」和「Rails 经验」部分总结了几个值得注意的技术点:

  1. 用户自定义函数(UDF):SQLite 的 Ruby gem 支持 UDF,团队用它实现了 regexpifstddev 等 MariaDB 中存在但 SQLite 缺失的函数,避免了对大量 SQL 语句的修改。有趣的是,评论区有用户指出 SQLite 其实内置了 iif 函数。

  2. 无符号大整数:SQLite 不支持 unsigned bigint,Lobste.rs 之前使用 unsigned bigint 作为某些 ID 类型,迁移时全部改为有符号的 bigint。

  3. 排序规则(Collation):SQLite 的 NOCASE 仅支持 ASCII 字符的大小写折叠,不支持完整的 UTF-8 大小写处理。之前 MariaDB 使用的是 utf8mb4_general_ci

  4. 全文搜索表:建议使用 SQLite 的 Contentless-Delete Tables(FTS5 的一种模式),这并非默认选项。

  5. Rails PRAGMA 默认值:Rails 为 SQLite 设置的默认 PRAGMA 对 Lobste.rs 来说运转良好。

  6. 数据库迁移文件:不同数据库的 migration 语法存在差异,需要将旧的数据库特定迁移文件分离出来。

放在更大的 SQLite 浪潮中看

Lobste.rs 的这次迁移并非孤立事件。过去几年里,SQLite 在 Web 生产场景中的使用正在增长。背后的推动力包括多个方面:

  • Litestream 解决了 SQLite 的实时备份和灾难恢复问题,通过持续复制 WAL 日志到 S3 等对象存储实现近实时的异地备份。
  • Turso / libSQL 在 SQLite 基础上增加了分布式能力,支持边缘部署和多区域复制。
  • Cloudflare D1 将 SQLite 作为其 serverless 数据库产品的基础。
  • 越来越多的开发者开始质疑「每个 Web 应用都需要一个独立数据库服务器」这一默认假设。

不过,从 Lobste.rs 的经验来看,SQLite 生产化仍然需要一些「知其所以然」的工作——性能调优、默认配置的调整、兼容性差异的处理,以及对单写者并发模型的接受。

结语

Lobste.rs 的这次数据库迁移提供了一个具体的参考案例:一个中等规模的 Web 应用(据评论区信息,数据库文件约 500MB),从客户端-服务器模式的 MariaDB 迁移到嵌入式 SQLite,经过一次失败和细致的性能调优后,获得了更低的资源消耗和更简单的运维体验。

这个案例或许不适合所有场景——高写入并发、多服务器水平扩展、复杂事务处理等需求仍然是 SQLite 的天然短板。但对于「单机足以承载、读远多于写」的 Web 应用来说,它展示了一条可行的路径。

本文的素材来自公开信息和社区讨论。如果你对这个话题有更深入的一手经验,欢迎指出文中的不足。

参考链接

  • Lobste.rs 站内帖:lobste.rs is now running on SQLite(280 分,43 评论)
  • GitHub issue #539:讨论从 MariaDB 迁移到 PostgreSQL / SQLite
  • Litestream:SQLite 实时复制工具文档
  • Turso / libSQL 项目
  • Rails SQLite 配置指南