2026 年 8 月 12 日,Tailscale 工程師 Alex Chan 發表長文,還原一場長達半年的追獵:公司控制平面底下的 SQLite 資料庫半年內損壞 19 次,找不到任何重現步驟,最後抓到的竟是 SQLite 原始碼裡潛伏至少 16 年的資料競爭。SQLite 官方文件如今為它立了專節,就叫「WAL reset bug」。Hacker News 上的討論衝上 1,223 分——工程社群對這類「深海怪物」故事毫無抵抗力。
潛伏十六年的資料競爭
Bug 本體如今寫在 SQLite 官方文件裡:檢查點(checkpoint)與寫入交易之間的一場罕見競爭。當檢查點進行到一半、恰好有一筆寫入落下,檢查點會誤判某些頁面已經從 WAL 複製進主資料庫——實際上沒有。結果是已提交的資料無聲消失,資料庫檔案損壞。SQLite 團隊估計這個缺陷已存在「至少 16 年」;觸發條件太苛刻,連他們自己的測試都得特地寫程式去撞出來。
十九次損壞,生產環境鑑識
Tailscale 的控制平面每個分片跑一個單寫入者的 SQLite 資料庫。半年 19 次損壞,毫無規律,常規測試全數通過。他們的解法是在生產環境做鑑識:部署遙測抓證據,用交易重放管線證明「已提交的資料在無錯誤的情況下消失」,再從指標裡找到破綻——檢查點從 WAL 複製的頁數,比檔案裡存在的還多。止血措施同步展開:一偵測到損壞就硬停服務,用自動化 PRAGMA integrity_check 盯住備份,並把復原流程演練到一小時內完成。
與上游協作:從追蹤墊片到修復
轉折點來自一份專業支援合約。SQLite 團隊寫了一個名為 tmstmpvfs 的 VFS 追蹤墊片;下一次事故的日誌,終於把競爭現場完整記錄下來。修復本身非常克制:在檢查點函式裡加一個檢查,偵測 WAL 是否被其他執行緒重置。過程還有一段插曲——修復先隨 3.52.0 發布,但該版因為另一個缺陷(文字轉浮點的捨入改變破壞了運算式索引)被撤回,只帶 WAL 修復的 3.51.3 才是乾淨版本。Tailscale 也把時間戳精度降到整數秒自保;SQLite 後續在 3.53.0 加入了自癒索引。
給工程團隊的三個教訓
第一,極稀有的競爭只在真實流量裡現形:重現不了的 bug,唯一抓手是可觀測性,而 Tailscale 的交易重放管線證明生產環境可以做鑑識,不只做監控。第二,資料庫損壞要按「一定會發生」來設計——完整性檢查、乾淨備份、演練過的復原手冊,把平均復原時間壓到小時級。第三,跟上游維護者正規合作,比自行 patch 更省:從 VFS 墊片、根因分析到修復進主線,都是合約買來的通道;十六年的老 bug 也修得動。
參考來源
- Tracking down the 16-year-old WAL-reset SQLite bug — Tailscale
- The WAL Reset Bug — SQLite Documentation
- Hacker News 討論串
本文由 AI 協助自上述來源整理,經人工審核後發布。
