SQLite 生产环境实战:用单个文件运行在线商店的经验教训

查看原文 HN 讨论

文章摘要

这篇来自 ultrathink.art 的博客文章,记录了作者使用 SQLite 作为生产环境数据库来运行一个在线商店的真实经历,分享了从最初的简洁优雅到遭遇并发写入灾难、最终找到务实解决方案的全过程。

文章首先阐述了选择 SQLite 的理由:对于单服务器部署且写入量适中的场景,SQLite 能够消除整整一类基础设施复杂性——无需连接池调优、无需数据库服务器升级、无需处理主从复制延迟。整个数据库就是一个文件,部署和备份都变得极为简单。

在 WAL(Write-Ahead Logging)模式方面,文章做了深入解析。WAL 模式改变了写入模型:写入者不再直接修改数据库文件,而是将变更追加到独立的 -wal 文件中,读取者则继续从主文件读取。这使得多个读取者和单个写入者可以并发操作。Rails 8 已默认为 SQLite 数据库启用 WAL 模式。

然而,文章最精彩的部分是关于一次真实生产事故的详细复盘。作者在两小时内进行了 11 次部署(push straight to main 直接部署到生产环境),使用 Kamal 容器编排工具。由于部署间隔太短,多个容器实例同时运行,三个进程同时持有同一个 WAL 文件并试图写入。结果,订单 16 和订单 17 在 Stripe 支付平台上成功完成了扣款(payment intents 显示 succeeded),但对应的订单记录却从未写入数据库。在 WAL 文件争用中,这些写入被静默丢失了。

这次事故的根本原因不是 SQLite 本身的缺陷,而是部署流程中的疏忽。文章详细描述了采取的修复措施:他们创建了一个 CLAUDE.md 治理文件(供所有 AI 编程代理遵循的规范),其中明确规定了”避免密集推送到 main 分支——2 小时内 11 次推送导致了 Kamal 部署重叠和 SQLite 并发访问问题”。解决方案是程序性的而非技术性的:停止每十分钟就推送到 main,改为批量合并相关变更。

在备份策略方面,文章强调必须使用 sqlite3 .backup API 来安全处理 WAL 模式下的备份,因为直接 cp 复制可能会抓取到半写入状态的文件。文章还指出了 SQLite 生产环境使用的核心约束:单服务器,并且需要谨慎控制部署节奏。当未来需要水平扩展或真正的多写入并发时,就需要迁移到 PostgreSQL。

HN 评论精华

  1. 对直接推送到 main 的惊讶:最受关注的评论之一是对作者工作流程的震惊——”等等,你直接推送到 main?等等,你让 Claude(AI)推送你的电商代码直接到 main,而且立刻就触发生产部署?”这引发了关于 AI 辅助开发中代码审查和部署安全的激烈讨论。

  2. WAL 模式并非银弹:多位有经验的评论者指出,WAL 文件在没有检查点(checkpoint)的情况下会无限增长。正确的备份流程应该是先获取”写入意图”锁(BEGIN IMMEDIATE),防止其他连接写入 WAL 文件,然后再进行备份。简单的文件复制在并发写入场景下是不安全的。

  3. SQLite 的适用边界讨论:评论区对 SQLite 在生产环境的适用范围展开了理性讨论。共识是:对于单服务器、中等写入量的应用,SQLite 确实是优秀的选择;但所有进程必须在同一台主机上运行,WAL 模式无法在网络文件系统上工作。一旦需要多服务器扩展,就必须考虑迁移方案。

  4. 中小规模 SaaS 的 SQLite 实践:有评论者分享了自己运行年收入六位数的 SaaS 产品使用 SQLite 超过一年的经验,认为对于绝大多数中小型应用来说,SQLite 的简洁性带来的运维优势远超其限制。关键是要充分理解其并发模型,不要盲目追求”企业级”数据库。