摘要: 2026 年 8 月 12 日,Tailscale 工程師 Alex Chan 發表了一篇長文,交代他們花了整整半年才收網的一場追查:公司控制平面底下那些 SQLite 資料庫,六個月內壞掉 19 次,重現步驟一個也拿不出來,而最後逮到的元凶,是藏在 SQLite 原始碼裡至少 16 年的一場資料競爭。SQLite 官方文件如今專門開了一節來寫它,名字就叫「WAL reset bug」。這則故事在 Hacker News 上拿下 1,223 分——這種「深海怪物」等級的除錯記事,工程社群向來毫無抵抗力。
2026 年 8 月 12 日,Tailscale 工程師 Alex Chan 發表了一篇長文,交代他們花了整整半年才收網的一場追查:公司控制平面底下那些 SQLite 資料庫,六個月內壞掉 19 次,重現步驟一個也拿不出來,而最後逮到的元凶,是藏在 SQLite 原始碼裡至少 16 年的一場資料競爭。SQLite 官方文件如今專門開了一節來寫它,名字就叫「WAL reset bug」。這則故事在 Hacker News 上拿下 1,223 分——這種「深海怪物」等級的除錯記事,工程社群向來毫無抵抗力。
潛伏十六年的資料競爭
這個缺陷現在已經收錄在 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 也照樣修得動。
