
2026/07/18 2:45
SQLite を少し学ぶ
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
この経験から得られた主要な教訓は、Django サイトを SQLite で運用するにはコードの最適化だけでなく、能動的なデータベース保守が必要であるということです。著者は統計情報を無視したことで重大なクエリ遅延が発生し、ミリ秒単位のタスクが 5 秒間のフリーズに発展することを発見しました。これを解決するために、内部データ分布を更新して検索を高速化する
ANALYZE コマンドを実装し、大規模なクエリから小規模なバッチ処理へ切り替えることで、重い書き込みや長時間のクリーンアップ DELETE 操作時のロックタイムアウトを防ぎました。背景として、SQLite は "Mess with DNS" などの小さなプロジェクトでは 3 つの別々のデータベースファイルに分けてテーブルを配置することで何年も安定して動作しましたが、著者の自らのサイトでは restic を使用したバックアップでメモリ超過およびファイルロックの問題が発生し、AWS S3 への信頼性の高いストリーミングバックアップのために litestream に切り替えており、現在は 400 時間のバックアップを保持しています。プロジェクトは概ね 10,000 行程度に留まることが見込まれており、Django ORM クエリについてはこれらのデータベースレベルの調整以上のパフォーマンス最適化は行っていません。結局のところ、開発者は SQLite が魔法ではなく、統計分析やバッチ処理のような明示的な保守を行わない場合、負荷下や複雑なテキスト検索においてパフォーマンスが急速に劣化するという事実を忘れないようにしてください。この認識は、2022 年から SQLite を使用し始めて以来の 4 年後、以前には Postgres から SQLite への移行などと同様の成功を収めていたにも関わらず、つい最近 ANALYZE コマンドについて知らなかったという経緯から得られました。参照元には、 production 環境で Django と SQLite を使用するに関するブログ記事と、SQLite パフォーマンスチューニングに関する gist が含まれています。本文
Django プロジェクトにおける SQLite 運用の実践と知見
最近、Django 系サイトの開発でデータベースとして SQLite を採用しました。小規模サイトでの実装では十分にあり得る選択肢ですが、データベースシステム自体の複雑さと私の知識不足に直面し、運用上のいくつかの重要なポイントを学びました。今回はその経験をまとめます。
🛠️ WAL モードと ANALYZE の重要性
ブログ記事で推奨されている通り、初期設定として WAL(Write-Ahead Logging)モード を有効化しました。
ANALYZE
コマンドの劇的効果
ANALYZE完全全文検索のために SQLite の FTS5 を使用した際、表(約 4,000 行)に対するクエリが 5 秒 かかるという異常な遅延が発生しました。これを解決したのが以下のコマンドでした:
ANALYZE;
- 実行前: クエリ実行時間 約 5 秒
- 実行後: クエリ実行時間 約 0.05 秒(またはそれ以下)へ劇的に短縮
ANALYZE は、クエリプランナーが適切な判断を行えるようテーブルの統計情報を生成します。行数やパラメータに基づき、意図せずして発生していた「二次的な時間計算量」問題を解消しました。
⚠️ データベース保守の高難易度と課題
Django のタスク処理などにより不要な行が溜まることがあります。これをクリーンアップする際、以下の問題が発生しました。
クリーンアップ時の問題現象
- 長時間の実行: 大量のデータを
で削除すると、トランザクション内で Python コードが実行されるため 5 秒以上 かかる。DELETE - タイムアウト: 処理中に他のワーカーが書き込みを試み、設定した 5 秒というタイムアウトが発生する。
- クラッシュ: 書き込み不能によりワーカーがクラッシュし、結果として 仮想マシン(VM)全体がシャットダウン してしまう。
解決策と教訓
- 当面の対策: クリーンアップ処理を小規模なバッチ単位に分割して実行する。
- 根本的な解決: Postgres など、複数の書き込み源を同時に許容できる「本格的なデータベース」への移行を検討すべきと認識した。
- 理想のアプローチ: サイトダウンタイムを計画した保守期間を設定し、その時間を確保して作業を行うワークフローの確立が必要。
🚀 ORM クエリパフォーマンス戦略
現在、Django の ORM を使用しており、クエリのパフォーマンスチューニングには一切注力していません(
ANALYZE 除く)。
- 現状: 問題なく動作している。
- 規模: データ量が比較的小さい(おそらく 1 万件程度)。
- 戦略: 今後もこの規模を維持できることを前提に、 ORM ベースの記述を継続する。
💾 SQLite のバックアップ手法
バックアップから復元したかは検証していないが、「死の男スイッチ」的な監視機制を意識して運用している。
手法 1: restic(フルバックアップ)
ファイル全体を圧縮し、S3 に保存する方式。
# DB ファイルを VACUUM し圧縮 sqlite3 /data/calendar.db "VACUUM INTO '/tmp/calendar.sqlite'" gzip /tmp/calendar.sqlite # S3 アップロードと管理 restic -r s3://s3.amazonaws.com/some_bucket/ unlock restic -r s3://s3.amazonaws.com/some_bucket/ backup /tmp/calendar.sqlite.gz restic -r s3://s3.amazonaws.com/some_bucket/ snapshots # 古いスナップショットの削除と不要なブロックの廃棄 restic -r s3://s3.amazonaws.com/some_bucket/ forget -l 1 -H 6 -d 2 -w 2 -m 2 -y 2 restic -r s3://s3.amazonaws.com/some_bucket/ prune
手法 2: litestream(インクリメンタルバックアップ)
近年、OOM(メモリ不足)で restic が停止することがあり、より効率的だと感じた Litestream を試用開始。
- 実装: 設定ファイルを作成しコマンドを実行するのみ。
litestream replicate -config litestream.yml - 注意点: 設定で
(保存期間)を指定したが、実際の機能効率は未確認。retention: 400h - ストレージ: 現在は AWS S3 を使用しているが、認証操作が面倒なため、将来的に他社製 S3 互換ストレージへの移行を検討中。
🗄️ 単一 DB のみならずマルチ DB の活用
現在のプロジェクトでは単一のデータベースのみを使用していますが、以前の実践から有益な知見を得ています。
- 事例: 「Mess with DNS」プロジェクトの構築時
- テーブルを同一ファイルに配置する必要がないため、3 つの独立したデータベースファイルに分けて運用。
- 非常に有益であり、現在も安定して動作(2022 年より 4 年間)。
- 結論: Postgres から SQLite への移行は、このプロジェクトにおける優れた選択だったと判断する。
📝 おわりに
Web プロジェクト向けに初めて SQLite を使用したのは 2022 年頃ですが、
ANALYZE という存在を知ったのは今日です。技術の基本的な知識を得るまでには時間がかかりますが、今後もさらに基礎的な機能や運用ノウハウを学び続ける予定です。