本番環境でのSQLite:Litestreamが「万が一」を安全に変える方法

Database tutorial - IT technology blog
Database tutorial - IT technology blog

午前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プロバイダーがオフラインになったとしても、データは暗号化されたバケット内で安全であり、数秒で復元できることがわかっているからです。スタックはスリムに、バックアップはリアルタイムに保ちましょう。複雑さを導入するのは、トラフィックが本当にそれを必要とした時だけで十分です。

Share: