SQLite の「unable to open database file」が高負荷時刻に散発する問題を WAL 化で根治した話
SQLite の「unable to open database file」が高負荷時刻に散発する問題を WAL 化で根治した話 ある自立型トレーディングシステムの運用中に、SQLite が OperationalError: unable to open database file を散発的に投げる慢性バグを追い詰めて修正した記録です。「ディスクは空いている」「ファイルは存在する」のにエラーが出る、という一見矛盾した現象の正体と、その根治・緩和の二段構えの対策をまとめます。 WAL とは? 本題に入る前に、今回のキーになる WAL(Write-Ahead Logging) を簡単に押さえておきます。 SQLite には書き込み中のクラッシュからデータを守るための「ジャーナル方式」が複数あり、journal_mode PRAGMA で切り替えます。 delete(デフォルト) — ロールバックジャーナル方式。トランザクションを開始するたびに 元DB-journal というファイルを新規作成して変更前の内容を退避し、コミット時に削除する。書き込み中は DB 本体を直接書き換えるため、読み取りと書き込みが互いをブロックする。 WAL — 先行書き込みログ方式。変更を DB 本体ではなく追記専用の 元DB-wal ファイルに追記していき、後で「チェックポイント」で本体に反映する。-wal と -shm(共有メモリインデックス)は一度作られたら使い回されるため、トランザクション毎のファイル新規作成が発生しない。 WAL の主な利点: 特徴 効果 読み書きが互いをブロックしない reader はトランザクション中の writer を待たずに読める(その逆も) ジャーナルファイルを毎回作らない ファイル作成のオーバーヘッドと競合がなくなる 書き込みが概ね高速 fsync 回数を減らせる(特に synchronous=NORMAL 併用時) WAL が向いているケース: 読み取りが多く・書き込みもそれなりにある、同一ホスト上の単一プロセス/複数プロセスからのアクセス。今回のように「書き込みデーモン 1 つ+複数の reader」という構成は典型的な適用例です。 WAL の注意点: ① ネットワーク越しのファイルシステム(NFS など)では共有メモリが使えず非対応、② in-memory DB では効かない、③ -wal / -shm ファイルが DB 本体と併存するため、バックアップは単純な cp ではなく専用 API(後述)を使う必要がある。 ...