「すべてのスマホアプリに潜んでいた16年物のバグ」——SQLiteに隠された競合状態の真実

データベースSQLiteインフラ

データソース:HN + web research · HN

「すべてのスマホアプリに潜んでいた16年物のバグ」——SQLiteに隠された競合状態の真実

2026年8月、リモートネットワーク接続ソフトウェアを手がけるTailscale社が自社データベースのポストモーテム(事後検証レポート)を公開した。過去半年間に原因不明的データベース破損が19回も発生し、そのたびに一部のユーザーネットワークが一時不通になり、復旧には毎回1時間以上を要していた。エンジニアチームが数ヶ月にわたって追跡調査を行った結果、引き金となっていた元凶は、世界で最も普及しているデータベースソフトウェアの中に少なくとも16年間潜んでいた。

Tailscaleは複数の端末を安全にプライベートネットワークへ接続するツールを提供している。同社に馴染みがない人にとっても、このストーリーは他人事ではない。なぜなら、今回の事件の主役であるデータベースは、あなたが日常的に使っているスマートフォンの中で今文字通り動いているからだ。

あなたが毎日使いながら、存在を意識しないソフトウェア

まず主役を紹介しよう。SQLiteは世界で最も広く利用されているデータベースエンジンであり、公式発表によれば数千億台規模のデバイス上で稼働している。日常的に使うメッセージアプリ、決済アプリ、Webブラウザ、ゲームなど、ほぼすべてのアプリがデータ保存にSQLiteを採用している。

SQLiteの最大の特徴は「組み込み型(Embedded)」である点だ。一般的な大型データベースが厳重に管理された銀行の金庫(独立したサーバープロセス)だとすれば、SQLiteは各アプリが持ち歩く小さな手提げ金庫のようなものだ。インストールも不要、プロセス管理も不要、アプリを起動するだけでそのまま利用できる。開発者はデータを放り込んでおくだけで、何十年間も手間をかけずに運用できる。

この圧倒的な使い勝手の良さにより、SQLiteはデジタル世界の不可欠な基礎インフラとなった。世界中の無数のデバイスで絶えず検証されているため、その信頼性はきわめて高く、あらゆるエラーの発生確率は極限まで抑え込まれている——開発者本人でさえ「バグは存在しない」と確信するほどに。

土台の中に潜んでいた瑕疵

Tailscaleは2022年以降、コアデータの保存先としてSQLiteを採用してきた。理由は「信頼性が高く、広く使われている」からであり、エンジニアの隠語でいう「退屈なテクノロジー(Boring Technology)」だからだ——ここでの「退屈」とは、トラブルが起きず安心して使えるという最高の褒め言葉である。実際、2023年初頭から2025年夏までの間、障害は一度も発生しなかった。

しかし2025年8月、バックアップパイプラインが突如アラートを吐いた。特定のデータベースファイルが破損している词というのだ。チームは修復と調査を行ったが、原因は特定できなかった。然后2回目、3回目……半年間で事故は合計19回に及んだ。大規模システム運用においては、単回の発生確率がいくら極小であっても、規模と試行回数が十分に大きくなれば、稀な事象もいずれ「日常業務」と化す。

エンジニアをさらに苦しめたのは、19回の障害の間に共通点が一切見当たらないことだった。同一のサーバーでも、同一のユーザー群でも、同じ時間帯でも、共通のワークロード特性法でもなかった。ラボ環境で再現させる手がかりすら存在しなかった。

「まずメモ用紙に書き、後で本帳簿へ転記する」書込モード

後の解明プロセスを理解するには、SQLiteの動作モードの一つである「WAL(Write-Ahead Log、ライトアヘッドログ)」の仕組みを知る必要がある。

大帳簿による記帳をイメージしてほしい。通常のモードでは、取引が発生するたびに大帳簿を直接開いて記入する。一方、WALモードでは、まず束になったメモ用紙へ迅速に書き込み、一定量が溜まった段階でまとめて大帳簿へと転記する。このメモ用紙が「WALファイル」であり、大帳簿が本来の「メインデータベースファイル」である。そして転記する一連の処理には「チェックポイント(checkpoint)」という固有の名前がついている。

WALとデータベースファイル

図:新しいデータはまずWALファイルに書き込まれ、その後メインデータベースファイルに転送される。出典:tailscale.com

WALモードのメリットは明確だ。分厚い大帳簿を毎回更新するよりもメモ用紙に書くほうが圧倒的に速く、転記作業は手の空いた時間に非同期で行うことができる。また、大帳簿を閲覧する読み手と、メモを書く書き手が互いに干渉しない。パフォーマンスを重視する多くのアプリがこのモードを採用している。TailscaleはWALを採用しただけでなく、迅速なバックアップのためにチェックポイントを極めて高頻度で手動実行するという積極的な運用を行っていた。この設計判断が、後にすべてのドラマを引き起こす導火線となる。

競合状態:二つの処理が衝突した瞬間

バグの発生メカニズムを一口で言えば、「データの転記処理」と「新しい記帳処理」が同時に発生した際、処理手順の整合性が崩れたことにある。専門用語で「競合状態(Race Condition)」と呼ばれる現象だ。

具体的にはこうだ。チェックポイント担当者がメモ用紙を確認し、10枚分のデータをメイン帳簿へ転記し始めた。ところが、転記の最中に別の処理が新しいメモを追加し、メモ用紙の連番カウントをリセットしてしまった——バグの名称「WAL-Reset」はここに由来する。チェックポイント担当者はリセットに気づかず、古い連番の前提で転記を継続した。その結果、本来まだ転記していないはずのデータページを「すでに転記済み」と誤認してスキップしてしまった。

チェックポイントのフロー

図:チェックポイント処理がWAL内のデータページをメインデータベースファイルにコピーする。出典:tailscale.com

その結果、特定の取引データが跡形もなく消え去った。さらに悪いことに、メイン帳簿の他のページには消えたデータへの参照(インデックス)が残されていたため、データベース全体が「破損状態」と判定された。Tailscaleのエンジニアは後日、ログの中に矛盾した数値を発見した。WALファイル内には10ページ分のデータしか存在しないにもかかわらず、チェックポイント処理が「20ページ分をコピーした」と報告していたのだ。超過した10ページこそ、古い連番を参照したことで生じた幻影であり、事件解決の決定的な手がかりとなった。

なぜ16年間も隠れ潜むことができたのか

SQLiteの公式開発チームの試算によれば、このバグは少なくとも16年前からコードベースに存在していた。これほど長期間発見されなかった理由は、発現条件があまりにも苛酷だったからだ。特定のライブラリバージョンの組み合わせ、チェックポイント処理中のわずか数マイクロ秒の隙間に書き込みが重なるタイミング、そして特定OSのファイルシステム動作——これら3つの条件が重なる確率は、公式開発者の言葉を借りれば「通常の利用下ではほぼ発生し得ない」レベルだった。

実際、SQLiteの開発者は修正プログラムの有効性を検証するにあたり、コード内に特殊なテスト用ロジックを挿入して意図的に衝突状態を作り出さなければならなかった。彼らが単一のバグのためにここまでの措置を講じたのは史上初のことだった。

大半のユーザーはデフォルト設定で運用しているため、生涯このバグに遭遇することはない。Tailscaleが19回も遭遇したのは、独自の超高頻度チェックポイント運用によって衝突の確率を何乗にも引き上げていたからだ。「万に一つの確率」が、彼らの環境では「時間の問題」へと変化していたのである。

探偵たちはいかにして謎を解いたか

数多くの障害振り返りレポートが存在するが、本件の分析プロセスの緻密さは業界でも群を抜いている。

最初の数ヶ月間、調査は完全に暗礁に乗り上げていた。自社の関連コードをすべて精査しても落ち度は見つからず、再現性もない。途中で6週間にわたり障害がピタリと止まった時期があり、一見解決したかのような錯覚を与えた——しかし、障害が発生しないことはバグが消えたことを意味しない。そこでチームは、データベースに対するすべての変更命令を独立したログファイルに克明に記録し始めるという決断を下した。

このログが突破口となった。その後の2回の事故においてログを再現再生(リプレイ)したところ、確かに書き込みが成功して確定(コミット)されたはずのデータが、後から跡形もなく消えている現象が確認された。書き込みデータが沈黙したまま消失する——这是ACID特性を前提とするデータベースの世界では通常あり得ない事態だ。

続いて、SQLiteのコア開発者たちが参戦した(Tailscaleは商用サポート契約を締結した)。両者は共同で tmstmpvfs shim と呼ばれる専用のデバッグツールを構築した(shimとは既存モジュールの外側を覆う監視シェルのこと)。SQLiteの内部構造は、大きく分けて3層に分かれている。最上層がSQL文を解釈し、中間層がデータをブロック単位に分解し、最下層が実際にディスクへデータを書き込む。この最下層がVFS(Virtual File System)と呼ばれるストレージ層インターフェースだ。監視用shimをVFSの外側に装着することで、金庫の前に置かれた高精度監視カメラのように、ディスクに対するすべてのI/O操作をミクロ単位で記録した。

VFS shim デバッグレイヤー

図:SQLiteの最下層ストレージインターフェースが監視レイヤー(shim)で包まれている。出典:tailscale.com

カメラを設置した後、彼らは次の事故を待った。待望の事故が発生すると、完全なI/OログがSQLiteのコア開発者に提出され、チェックポイントの最中に書き込みトランザクションがWALをリセットし、チェックポイント側がそれを検知できていないという競合状態の現場が白日のもとに晒された。

ほぼ同時に、別の角度からも決定的な証拠がもたらされた。ソフトウェア検証の専門企業Antithesisが、「プロパティベーステスト(Property-based Testing)」という手法を用いてこのバグを独立して再現させることに成功したのだ。大量のランダムな並行書き込みと並行チェックポイントの操作シーケンスを自動生成し、「コミットされたデータは絶対に失われない」「データベースは絶対に破損しない」という2大原則を監視し続けた。テストツールは修正前のバージョンで違反を検知し、修正後のバージョンで正常動作を確認した。人間では思いつかない複雑な操作順序を、機械の圧倒的な試行回数が証明してみせたのだ。

修正、そして誤報の嵐

修正自体はわずか1箇所の変更で完了した。チェックポイント処理の内部に検証ロジックを追加し、他のスレッドによってWAL状態がリセットされたことを検知した場合、安全に処理を中断させるというものだ。この修正はSQLite 3.51.3として正式リリースされた。

しかし、物語はここで終わらない。Tailscaleが修正版を本番環境へデプロイした直後、バックアップ監視ダッシュボードが破損アラートで真っ赤に染まった。結論から言えば、これは誤報だった。旧バージョンで生成されていた潜在的なインデックスの齟齬が、新バージョンの最適化によって表面化したためだった。SQLite開発チームは直ちに該当バージョンを取り下げ、純粋なWAL-Reset修正のみを含んだビルドを再リリースした。Tailscale側もデータ構造を調整して回避策を講じた。一つのバグを直したことで別の問題が顕発する——こうした連鎖反応はシステムエンジニアリングにおいて極めて普遍的であり、「修正完了」が容易なゴールではないことを示している。

そして最後の仕上げが行われた。修正が本番環境で真に効果を発揮しているかを確認するため、Tailscaleは「書き込み」と「WALリセット」の衝突が発生した際にログを記録するアラートを仕込んだ。デプロイ後、2ヶ月間は静寂が保たれた。しかし2ヶ月後、ついに警報が鳴り響いた——16年間隠れていた競合状態が、まさに自社の本番環境で再現し、修正コードによって確実にブロックされたことが証明された瞬間だった。その後4ヶ月間、データベースの破損事故は1件も発生していない。

最も信頼されるものこそ、最も疑うに値する

この事件は、システム開発における味わい深い教訓を示している。SQLiteは世界で最も厚い信頼を寄せられているソフトウェアの一つであり、採用時にその信頼性を疑うエンジニアは存在しない。しかし、まさにその「当たり前の信頼」こそが、バグを16年間にわたって潜伏させ、数千億台のデバイスに影響を及ぼし続ける要因となった。最下層の基盤が崩れた時、その上に築かれたあらゆるアプリケーションが被害を受け、しかも誰も基盤のバグを疑おうとしない。

この出来事は、エンジニアリングにおける重要な示唆を与えている。依存度が高いコンポーネントほど、最も愚直で厳密な手法によって検証されるべきなのだ。Tailscaleが取った一連の真摯な対応は、業界のエンジニアから絶賛された。技術コミュニティのコメント欄で最も多く見られた言葉は「Refreshing(清々しい)」だった。彼らは他人に責任を転嫁せず、SQLite公式の商用サポートを購入し、さらにオープンソースのデバッグツールの開発費用を資金援助することで、世界中の開発者が将来同様の問題に対処できるように貢献した。

自社の障害対応のためにオープンソースプロジェクトへ専用デバッグツールの開発資金を提供する企業は、ビジネスの世界ではきわめて稀だ。スマートフォンの中で静かに働き、決して存在を主張しない基礎ソフトウェアの裏側には、めったに起きない「あり得ないエラー」を撲滅するために半年間を捧げるエンジニアたちの執念が存在している。

16年越しのバグはついに解明された。では、次のバグはどこに潜んでいるのだろうか?案外、あなたが最も信頼しているスマホアプリの奥底で、静かにその時を待っているのかもしれない。

参考リンク:

  • Tailscale: SQLite WAL-Reset Bug レビュー
  • Antithesis: Breaking the WAL
  • HN 議論 (item?id=49272832)