16年見つからなかったSQLiteのバグ。「壊れない前提」はどこまで置けるか | GH Media
URLがコピーされました

16年見つからなかったSQLiteのバグ。「壊れない前提」はどこまで置けるか

URLがコピーされました
16年見つからなかったSQLiteのバグ。「壊れない前提」はどこまで置けるか

原因の分からないデータ破損が、数ヶ月にわたって断続的に起きる。再現手順が作れない。コードレビューでも見つからない。ログにも異常が残らない。この状況で、自分たちの書いたコードを疑い続けるのは自然な判断です。

Tailscale が半年かけて追った末にたどり着いた原因は、そこにはありませんでした。SQLite 本体に、2010年から16年間残っていた競合状態のバグでした。SQLite 開発チームはこれを「WAL-Reset バグ」と名付けています。

稀なバグの逸話として消費してしまうと、実務上は何も残りません。むしろ問うべきなのは、自社の業務システムが「この部品は壊れない」という前提をどこに置いているか、そしてその前提が外れたときに気づける作りになっているかです。

何が起きていたのか

SQLite の WAL(Write-Ahead Logging)モードでは、書き込みはいったん WAL ファイルに追記され、チェックポイントという処理でまとめて本体のデータベースファイルへ反映されます。

このとき、書き込みトランザクションとチェックポイントが特定の瞬間にちょうど重なると、SQLite は一部の WAL ページを「もうデータベース本体へコピー済み」と誤認することがありました。誤認されたページは本体に書かれないまま捨てられ、そのデータは永久に失われます。

厄介なのはここからで、失われたページを参照している他のページ(インデックスなど)は正常に書き込まれます。結果として、存在しない場所を指すインデックスが残り、データベースファイルが壊れた状態になります。書き込みは成功したように見え、壊れていることが分かるのはずっと後です。

踏む条件は限定的です。WAL モードであること、同じファイルに2つ以上の接続が別スレッドまたは別プロセスから開かれていること、そしてその2つが同じ瞬間に書き込みとチェックポイントを行うこと。すべてがそろったときだけ発生します。

どれくらい稀かというと、SQLite 開発チームはこの不具合を検証するために、意図的に発生させるコードをテスト環境へ追加する必要がありました。通常のテストでは踏めなかったということです。

WALモードで書き込みとチェックポイントが同時に発生した際、一部のWALページがコピー済みと誤認されて本体に書かれず、そのページを参照するインデックスだけが書き込まれることでデータベースが壊れる流れを示した図

影響範囲と、いま確認すべきこと

対象となるのは SQLite 3.7.0(2010-07-21)から 3.51.2(2026-01-09)までのバージョンです。修正は 3.51.3(2026-03-13)以降に入っています。

「うちは SQLite を使っていない」と判断する前に、一段確認が要ります。SQLite は単体で選んで導入するというより、別のものに同梱されて入ってくるタイプの部品です。

入り込み方確認する場所
言語ランタイムに同梱Python の sqlite3.sqlite_version、Go や Node.js のドライバが同梱するバージョン
アプリに組み込みデスクトップアプリ、モバイルアプリ、CLI ツールの依存ライブラリ
ミドルウェアの内部で使用管理用メタデータの保存先として内部利用しているケース

確認方法自体は単純で、SQL で select sqlite_version(); を実行すれば分かります。OS のパッケージ版とアプリが同梱する版が食い違っていることがあるため、アプリが実際にリンクしているほうを見るのが要点です。

Tailscale がこのバグを踏みやすかったのは、チェックポイントを自前で制御し、かつ非常に積極的に実行していたためでした。裏を返すと、SQLite に任せきりの一般的な使い方では遭遇確率はさらに下がります。ただし「確率が低い」は「起きない」ではありません。

6ヶ月かかった理由のほうが重要

このケースで実務に効くのは、バグの内容よりも原因にたどり着くまでに半年かかったという事実です。

調査が長引いたのは、疑う順番として自社コードが先に来るからです。これは正しい順番であり、10回のうち9回はそこで見つかります。問題は、そこで見つからなかったときに「では次はどこを疑うのか」が決まっていないことです。

多くの現場では、次の一手が用意されていません。結果として同じ場所を繰り返し調べることになります。用意しておくべきは、依存している部品のどれが「壊れない前提」で扱われているかの一覧です。データベース、ファイルシステム、ランタイム、クラウドの永続化層。これらは通常テストの対象外に置かれています。前提として置いているからこそテストしていないわけで、そこは意識的に書き出さないと出てきません。

正常な状態を先に定義しておかないと異常に気づけないという構造は、監視の設計と同じです。この考え方は監視は「正常」を定義することから始まるで扱っています。

発注側が確認できる3点

業務システムを外部に発注している立場でも、技術的な詳細に踏み込まずに確認できることがあります。

1. データの整合性を定期的に検査しているか。SQLite なら PRAGMA integrity_check、他のデータベースにも同等の仕組みがあります。壊れたことに何ヶ月も気づかない状態を避けるための最低線です。「バックアップは取っている」は、壊れたデータを壊れたままコピーし続けている可能性を排除しません。

2. バックアップから戻せることを、実際に試したことがあるか。取得の成功ログではなく、復元の実施記録を確認します。年に1回でも実際に戻していれば、気づかないうちに復元不能になっていた、という事態は避けられます。

3. 依存ライブラリのバージョンを、誰がいつ上げる契約になっているか。今回のように修正が上流で出ても、更新の責任者が決まっていなければ古いまま動き続けます。保守契約の中でここが曖昧なまま放置されているケースは珍しくありません。

3番目は、脆弱性が公表されたときにも同じ形で問題になります。更新されないまま動き続けるシステムのリスクは保守されないサイトが抱える脆弱性にまとめました。データベースの選定そのものを見直す段階ならSQLite と Postgres の使い分けが判断材料になります。

次にやること

自社のシステムで select sqlite_version(); を実行してみてください。3.51.3 より前なら、影響条件に当てはまるかを開発側に確認する価値があります。当てはまらなくても、確認できたこと自体に意味があります。どのバージョンが動いているか即答できない部品が他にもある、という事実が見えるからです。

そのうえで、上の3点のうち「実際に試したことがある」と言えるのがいくつあるかを数えてください。ゼロなら、稀なバグを心配する前にやることがあります。

既存システムの依存関係の棚卸しや、復旧手順の実効性の確認については、グリームハブの開発・AI・自動化のご相談で承っています。構成によって確認すべき範囲は変わるため、個別にご相談ください。お問い合わせからどうぞ。

Sources

URLがコピーされました

グリームハブ株式会社は、変化の激しい時代において、アイデアを形にし、人がもっと自由に、もっと創造的に生きられる世界を目指しています。

記事を書いた人

鈴木 翔

鈴木 翔

技術の可能性に魅了され、学生時代からプログラミングとデジタルアートの分野に深い関心を持つ

関連記事

「TECH」の記事一覧を見る