SQLiteの16年来のバグを追跡:Tailscaleの調査過程
昨年末から今年初頭にかけて、Tailscaleのサービス安定性に問題が生じていた。6ヶ月間で19件のデータベース破損が発生した原因は、SQLiteに16年間潜在していた「WAL-Reset bug」と判明した。SQLite開発者の支援を受けた調査を経て、修正版がSQLite 3.52.0およびSQLite 3.51.3でリリースされ、Tailscaleが制御プレーンにデプロイした後、4ヶ月間にわたってデータベース障害が発生していない。
Tailscaleは2022年からSQLiteをプライマリデータベースとして運用している。制御プレーンは調整サーバーであるシャードに分割されており、各シャードは単一のGoプロセスでアクセスされるSQLiteデータベースを保有している。昨年8月、データパイプラインが1つのデータベースの破損を報告し、その後、6ヶ月間で計19件の破損が確認された。Tailscaleは数分ごとにデータベーススナップショットを取得してS3にアップロードし、SQLiteのPRAGMA integrity_checkコマンドを使用して破損を検出している。破損されたデータベースはテイルネットとデバイスに関するメタデータを含んでおり、プライベート暗号化キーやネットワークトラフィックは含まれていない。
バグの特定とデバッグプロセス
初期の復旧ダウンタイムは1時間以上に及んだが、その後の事象では段階的に短縮された。Tailscaleは専門的なサポート契約を通じてSQLite開発者に連絡し、トランザクションロギングパイプラインを構築してSQL文を個別のログファイルにストリーミングした。2つの事象では、トランザクションログの再生に失敗し、特定のデータが後続のトランザクションに対して不可視となった。SQLiteはWrite-Ahead Logging(WAL)で実行され、Tailscaleはチェックポイントプロセスを手動で制御している。SQLiteのメトリクスは、利用可能なページ数より多くのページがWALからコピーされていることを示した。
SQLite開発者はtmstmpvfsシムと呼ばれるデバッグツールを作成した。バグは、チェックポイントと書き込みトランザクション間の稀なデータ競合であり、チェックポイント中の特定のタイミングで書き込みが発生すると、ページはコピーされたと認識されるが実際にはコピーされない状態が生じた。SQLite開発者はこれを「WAL-Reset bug」と命名し、少なくとも16年間SQLiteに存在していたと推定している。
修正とその後の展開
バグの修正はチェックポイント関数に追加のチェックを加えるもので、SQLite 3.52.0でリリースされた。Tailscaleは3.52.0をカナリシャードに最初にデプロイしたところ、バックアップモニターが13個のデータベースで破損を報告した。しかし、これらの破損は別の陳旧な式インデックスバグに起因する誤検知であった。SQLite 3.52.0は、テキストから浮動小数点への変換の丸め動作を変更する最適化を実装していた。SQLite開発者は3.52.0を取り下げ、代わりにSQLite 3.51.3をリリースした。Tailscaleはタイムスタンプの精度を整数秒に低下させることで問題を修正した。
SQLite 3.53.0には自動的にインデックスを修復する機能が搭載された。Tailscaleは、書き込みトランザクションとWAL-resetが重複する際に警告をログする修正をSQLiteドライバーに適用し、制御プレーン全体に修正をロールアウトした後、4ヶ月間にわたってデータベース障害が発生していない。
筆者の見立て
- 標準的ではない方法で退屈なテクノロジーを実行することはリスクであるという点で有用な示唆を与えている
- 一般的な使用パターンと標準設定は非常に十分にテストされており信頼性が高いと考えている
- チェックポイントプロセスを手動で制御し、独自の積極的なペースで実行することにより、よく踏み均された運用パスから外れたと解釈している
- 修正を適用することで、以前より強固な立場に置かれていると考えている
この記事は元記事の事実のみに基づいて自動生成されました。
出典
Tailscale、「How we tracked down a 16-year-old SQLite bug」、https://tailscale.com/blog/sqlite-wal-reset-bug