午前2時の惨劇
午前2時、ナイトスタンドの上でスマートフォンが振動し始めました。管理していた小規模なSaaSツールの監視ダッシュボードは、真っ赤に染まっていました。月額5ドルのVPSのSSDが致命的なハードウェア故障を起こしたのです。標準的なSQLiteの構成で運用していたため、データは何百マイルも離れたデータセンターの、動かなくなったディスクの中に閉じ込められてしまいました。最初に思ったのは、「月額15ドルのマネージドPostgresにお金を払っておけばよかった」という純粋な後悔でした。
私はこれまで、複雑なリレーショナルスキーマにはPostgreSQLを、迅速なプロトタイピングにはMongoDBを使ってきました。それらが業界標準であるのには理由があります。しかし、多くのサイドプロジェクトや中規模アプリにとっては、それらは過剰(オースペック)です。ネットワーク遅延が発生し、コネクションプーリングが必要になり、数百メガバイトものRAMを消費します。私は、クエリが0.1ミリ秒未満で実行されるSQLiteの圧倒的な速度を、「単一障害点」への不安なしに利用したいと考えていました。そこでLitestreamがすべてを変えてくれました。
課題:なぜエンジニアはSQLiteを恐れるのか
SQLiteが驚異的に速いのは、アプリケーションプロセス内で動作するからです。TCPのオーバーヘッドも、データベースサーバーを探す手間もありません。しかし、従来は本番環境における2つのリトマス試験(評価基準)で不合格となっていました。
- ディザスタリカバリ: サーバーのNVMeドライブが故障したり、プロバイダーが破産したりすれば、データベースは消滅します。
- ポイントインタイムリカバリ: 1時間ごとに2GBのファイルをコピーするcronジョブを実行するのは不格好です。次のバックアップの直前にクラッシュが発生すれば、59分間のユーザーデータを失うリスクがあります。
Litestreamは「サイドカー」プロセスとして動作することで、この問題を解決します。SQLiteのWrite-Ahead Log (WAL) を監視し、すべてのトランザクションをAWS S3やCloudflare R2などのクラウドストレージにほぼリアルタイムでストリーミングします。
仕組み:WALモードの魔法
Litestreamを使用するには、Write-Ahead Logging (WAL) を理解する必要があります。標準モードでは、SQLiteはメインのデータベースファイルに直接書き込みます。WALモードでは、SQLiteはまず変更内容をセカンダリの -wal ファイルに追記します。これにより、複数のリーダー(読み取り)と1つのライター(書き込み)が互いにブロックすることなく同時に動作できるようになります。
LitestreamはこのWALファイルを鋭く監視します。トランザクションがコミットされるたびに、Litestreamは差分をキャプチャしてS3バケットに送信します。サーバーが故障しても、新しいインスタンスを立ち上げるだけです。その後、Litestreamにデータの復元を指示します。最新のスナップショットを取得し、増分変更をリプレイ(再現)します。データ損失は実質ゼロです。
実践:SQLite + Litestreamの設定
堅牢なセットアップを構築しましょう。今回は、転送料(Egress fees)が無料で、10GBの寛大な無料枠があるCloudflare R2をターゲットとして使用します。小規模から中規模のアプリに最適です。
1. Litestreamのインストール
Litestreamは単一のGoバイナリです。軽量で依存関係もありません。Linuxサーバーへのインストールは数秒で完了します:
curl -LO https://github.com/benbjohnson/litestream/releases/download/v0.3.13/litestream-v0.3.13-linux-amd64.tar.gz
tar -xzf litestream-v0.3.13-linux-amd64.tar.gz
sudo install litestream /usr/local/bin/litestream
2. WALモードの有効化
アプリケーションでSQLiteにWALジャーナルを使用するように指示する必要があります。コード内(PRAGMAステートメントなど)で行うか、CLI経由で手動で行うことができます:
sqlite3 /var/lib/my-app/data.db "PRAGMA journal_mode=WAL;"
3. レプリケーションの設定
/etc/litestream.yml に設定ファイルを作成します。このファイルがローカルディスクとクラウドの架け橋となります。
access-key-id: <あなたの-r2-キー>
secret-access-key: <あなたの-r2-シークレット>
dbs:
- path: /var/lib/my-app/data.db
replicas:
- url: s3://マイバケット名.r2.cloudflarestorage.com/backup
4. Systemdによる自動化
これを手動で実行したくはないでしょう。サーバーの起動時にレプリケーションが開始されるよう、systemdサービスを使用します。/etc/systemd/system/litestream.service を作成します:
[Unit]
Description=Litestreamレプリケーション
After=network.target
[Service]
ExecStart=/usr/local/bin/litestream replicate
Restart=always
[Install]
WantedBy=multi-user.target
sudo systemctl enable --now litestream で起動します。
運命の瞬間:リカバリのテスト
リカバリをテストするまで、バックアップはただの「願い」にすぎません。サーバーの完全な消失をシミュレートしてみましょう。データベースファイルを(慎重に!)削除します:
rm /var/lib/my-app/data.db
さあ、死の淵から蘇らせましょう:
litestream restore -o /var/lib/my-app/data.db s3://マイバケット名.r2.cloudflarestorage.com/backup
数秒以内に、データベースが戻ってきます。本番のワークフローでは、この復元コマンドをDockerのentrypointやCI/CDパイプラインに追加すべきです。これにより、アプリは常に最新の状態で起動するようになります。
トレードオフ:あなたに適していますか?
このセットアップは多くのプロジェクトにとって「ちょうど良い(ゴルディロックス・ゾーン)」ものですが、あらゆるユースケースに対する銀の弾丸ではありません。
メリット:
- コスト: AWS RDSのマネージドPostgresは月額約15〜30ドルから始まります。この構成ならR2の無料枠で0ドルです。
- ゼロ遅延: アプリはローカルのNVMeドライブ上のファイルと通信します。データベースサーバーへのネットワークホップは発生しません。
- シンプルさ: コネクションプーリングや複雑なVPCピアリングを管理する必要はもうありません。
注意点:
- 単一のライター: SQLiteは並列読み取りを実に見事に処理しますが、一度に書き込めるのは1つのプロセスだけです。秒間500回以上の書き込みが発生する高トラフィックなアプリなら、Postgresを使い続けましょう。
- 垂直スケーリングのみ: これはシングルノードのアプリ向けです。10台のアプリケーションサーバーに水平スケールする必要がある場合は、CockroachDBやLiteFSのような分散型DBが必要です。
最後に
ハードウェアの故障を恐れるあまり、安易に重くて高価なデータベースを選ばないでください。SQLiteとLitestreamの組み合わせは、ローカルファイルのシンプルさを保ちながら、分散システムのようなレジリエンス(回復力)を提供してくれます。これにより、私の午前2時の悪夢は安らかな眠りへと変わりました。たとえ明日VPSプロバイダーがオフラインになったとしても、データは暗号化されたバケット内で安全であり、数秒で復元できることがわかっているからです。スタックはスリムに、バックアップはリアルタイムに保ちましょう。複雑さを導入するのは、トラフィックが本当にそれを必要とした時だけで十分です。

