追踪一个存在了16年的 WAL 重置 SQLite bug
一个罕见的 SQLite 预写日志(WAL)损坏 bug 在沉睡 16 年后,被 Tailscale 高并发、激进 checkpoint 的控制平面工作负载暴露出来;这促使他们资助 SQLite 维护者追查问题,并构建新的 VFS 级调试工具。评论者讨论了这一事件对 SQLite 在并发访问下可靠性的含义、它与 Postgres 这类客户端-服务器数据库的权衡,以及即便是极其全面的测试也有其局限。该线程还突出显示了 Tailscale 的工程文化——为上游支持付费、容忍但严谨地采用非标准架构,以及支持像 headscale 这样的开源替代方案。
Tailscale 对该 bug 的处理与开源支持
- 许多评论者称赞 Tailscale 购买 SQLite 专业支持,并资助了帮助隔离竞态条件的 VFS shim。
- 这被视为一种罕见的长期思维示例:为解决眼前问题付费,同时也为所有人改进工具。
- 有些人指出,这种模式在数据库领域很常见(例如 Percona/EnterpriseDB),也与 SQLite 公开提供的专业支持/联盟方案相符。
身份与认证选择
- Tailscale 只用 SSO(没有用户名/密码)的设计引发了讨论。
- 支持者的理由包括:避免承担身份提供方责任、缩小攻击面(撞库、账号农场),并让个人账户安全与企业客户保持一致。
- 批评者认为:magic links 和 SSO 可能让人感觉笨重;被 GitHub/Apple “永远”绑定也显得怪异,不过支持团队可以迁移账户或添加仅使用 passkey 的身份。
Headscale、遥测与替代方案
- Headscale(自托管控制平面)被提及为增强信任的因素;一些人很满意地在 NixOS 上运行它。
- 抱怨包括需要额外配置才能禁用遥测,以及在 iOS 上只能有限地退出(情况不明;一位 Tailscale 开发者表示这可能已经改变)。
- 一些重要功能如 App Connectors 目前还不能与 Headscale 一起工作,推动部分用户转向像 NetBird 这样的完全开源替代方案。
SQLite 与 Postgres 以及架构选择
- 讨论集中在:一个大型控制平面“应该”使用 SQLite 还是 Postgres/MariaDB。
- 支持者指出,SQLite 的设计明确支持单写多读模式,而且具备出色的持久性、性能和测试能力。
- 批评者认为,并发很难,bug 也很深;带内置在线备份的多写者系统可能会简化运维。
- 线程中的 Tailscale 员工表示,他们很早就选择了 SQLite,之后做了垂直扩展,如今依赖本地存储延迟;切换并不简单。
WAL 重置 bug 的细节与影响
- 该 bug 只影响 WAL 模式下、不同线程/进程中的多个连接,以及快速、手动的 checkpoint。
- Tailscale 用于备份的激进、非标准 checkpoint 策略,使他们更容易触发它。
- 有人指出,考虑到实际生产事故,SQLite 的更新日志和 bug 描述显得过于轻描淡写。
- 讨论还在澄清“复制的页比实际存在的更多”和“页面未被写入”之间看似矛盾的说法:内部计数器是错的,导致 SQLite 跳过了真正的写入。
单点故障与分片设计
- 分片数据库是每个分片各自的 SPOF:损坏会让该分片的控制平面失效,但不会影响全局数据平面,因为流量一旦建立就是点对点的。
- 有人认为,试图通过复杂的分布式共识去消除每一个 SPOF,可能反而会降低整体可靠性;成本/复杂度权衡很重要。
测试、形式化方法与 AI 工具
- 讨论强调了 SQLite 庞大的测试套件(数千万行),但一个存在 16 年的 bug 仍然漏过了。
- 这引出了“测试不能证明没有 bug”、穷尽测试的局限,以及状态空间爆炸等话题。
- 线程中分享了一个能复现该问题的 TLA+ 模型,以及 Antithesis 的确定性并发测试,据称可以在几分钟内找到这个 bug。
- 对于静态类型系统或 Rust 风格的无竞态能力是否有帮助,意见不一,因为这里很可能使用了 unsafe/内存映射 I/O。
总体情绪
- 整体语气是:敬佩调试工作的难度、SQLite 的质量,以及 Tailscale 的透明度和资助策略;同时也对在极高并发、高规模、非标准方式下推动 SQLite 持保留态度。