lobste.rs、MariaDBからSQLiteへ移行
(lobste.rs)- lobste.rs は MariaDBからSQLiteへ移行した後、月曜のトラフィック急増まで安定して処理し、CPU・メモリ使用量の低下と、より速い体感レスポンスを確認した
- 別途運用していた MariaDB VPS を停止すれば VPS コストは半減し、2019年に始まったマイグレーション課題も完了する
- 初回デプロイでは読み取り専用トラフィックだけで全 CPU が 100% に達してロールバックしたが、大規模テーブルでの 全テーブルスキャン 2件と N+1 問題を修正した後、再デプロイに成功した
- SQLite のユーザー定義関数で
regexp・if・stddevを実装する一方、unsigned bigint 非対応や ASCII のみを処理するNOCASEなど、互換性の制約を解決する必要があった - 本番データベースにアクセスせずに基盤 DB を変更するには、現実的な規模のテストデータ、テストスイート、デプロイ・ロールバックのチェックリスト、そして密接な協力が必要である
2019年の議論から SQLite 移行まで
- マイグレーションの議論は 2019年の issue #539 で始まった
- 当時は MariaDB との互換性を考慮して MySQL を代替案として提案したが、すでに PostgreSQL への移行議論が進んでいた
- 2025年に Rahul が K1 による MariaDB 買収 に言及し、PostgreSQL マイグレーションの詳細な議論が続いた
- 2月には Rahul が lobsters を SQLite で動かせるか と質問し、2025年6月からプロジェクトへの参加が本格化した
- 2025年8月の 最初の PR は、作業が中断された後に GitHub 上で古い PR として閉じられ、再オープンできなかった
- 2つ目の PR には、性能テスト、自前の DB 間マイグレーションスクリプト、データ整合性に関するデバッグが含まれていた
- 既存の MariaDB/MySQL→SQLite 変換スクリプトの中に要件を満たすものがなかったため、別途スクリプトを作成した
初回デプロイ失敗と性能ボトルネックの修正
- 2月21日の初回デプロイは デプロイチェックリスト を準備して実施したが、デプロイ直後、読み取り専用状態のトラフィックだけで全 CPU が 100% に達した
- 原因を特定できず、ロールバック した
- 本番 DB にアクセスできず、実際の性能問題を事前に再現するのは困難だった
- 失敗の2日後、3つ目にして最終の PR を開いて検索関連の問題を修正し、実際の lobsters データの半分の規模をローカルで生成する 大規模データ生成スクリプト を追加した
再デプロイの結果と運用コスト削減
- 7月11日の2回目のデプロイでは、デプロイ・ロールバックのチェックリストを再度用意し、移行後もサイトは正常に稼働し、CPU とメモリ使用量も安定していた
MariaDB と異なる SQLite の制約
- SQLite gem の ユーザー定義関数 (UDF) サポートを利用し、SQLite に存在しない
regexp,if,stddevを実装した- SQL マイグレーション向けの回避処理を過剰に追加せずに済んだ
- MariaDB で一部の ID に使っていた unsigned bigint を SQLite がサポートしていないため、bigint に変更した
- 照合順序も MariaDB より制限がある
- MariaDB では
utf8mb4_general_ciを使っていたが、SQLite ではNOCASEを適用した NOCASEは UTF 全体の大文字小文字折りたたみをサポートせず、ASCII 文字のみ処理する
- MariaDB では
- SQLite の全文検索テーブルには、デフォルトではない推奨方式の Contentless-Delete Tables が適していた
- Rails の デフォルト PRAGMA 設定 は lobsters で問題なく動作した
- データベースマイグレーションは DB ごとに異なるため、既存のマイグレーションを old_migrations ディレクトリへ移し、
db:migrateが引き続き動作するようにした
- データベースマイグレーションは DB ごとに異なるため、既存のマイグレーションを old_migrations ディレクトリへ移し、
テストと協力から得た教訓
- lobsters のコードベースでは、検索パーサー や、レンダリング速度を高めるハックである heinous_inline_partials を確認した
- テストスイート により、大規模な手動テストを行わずに SQLite 移行の正確性を検証できた
- 参加者どうしの円滑なコミュニケーションがマイグレーション成功を左右した
- 本番 DB にアクセスできない状態で基盤データベースを置き換えるのは非常に難しかった
- 同じ作業をやり直すなら、事前に 現実的なデータ規模 を用意すべきだった
- テスト中に全テーブルスキャンが発生したら失敗するよう設定できていれば、初回デプロイの性能問題を事前に発見できたはずだ
- 本番に近いデータセットを自分で書いたり、1週間も待ったりせずに容易に作成できるツールが必要である
1件のコメント
Lobste.rsの意見
@thomas0 が大規模な移行を丁寧に担当し、@355E3B は計画と運用を支援し、#539 の関係者や #lobsters のおかげで読み取り専用の時間も楽しく乗り切れた
もともと #539 では PostgreSQL を予定していたが、実際の作業支援者が SQLite を使っており、想定需要より大きく複雑な解決策を避けたかったため SQLite を選択した。PostgreSQL は普段の第一選択だが、別サービスを運用・調整・保守しなければならない負担がある
驚いたことに移行後は CPU と RAM の使用量がともに減少しており、詳しいグラフは #539 の最後の部分 にある
SQLite のデフォルト設定を見るたびに驚かされる。WAL、外部キー、
synchronous=NORMALなどをデフォルトで有効にする新規プロジェクト向けモードがあるとよく、そのほかにも改善すべきデフォルト値がありそうだSQLite データベースの バックアップ戦略 が気になる。litestream のようなツールを使っているのか、別の方法なのか知りたい
lobste.rs では 更新処理 をどう扱っているのか気になる
SQLite にはすでに iif 関数 があるが、同じ領域で ユーザー定義関数 を運用していること自体も興味深い
SQLite は PostgreSQL や MySQL と同程度の 同時実行性 をサポートしていないと思っていたが、概要ドキュメント を見ると、いまは複数プロセスから使えるものの、lobste.rs は単一サーバーで動かす必要があると理解すればよいのか気になる
プロセス同士は共有メモリや
mmapのような仕組みで SQLite 内部で同期され、同時書き込みは 1 つだけ可能で読み取りは複数許可されるのか、待機中の書き込み処理はキューで順番を待つのかも知りたい書き込み待ちは明示的な共有メモリキューではなく、少し待ってから再試行する方式が使われているようだ。複数マシンで SQLite を使うのが完全に不可能かは断言できないが、NFS でファイルを共有する単純な方法は明示的に推奨されていないため、単一サーバー と理解するのが妥当な近似であり、今どきは 1 台でもかなりの規模を処理できる
busy_timeoutを機能させるために即時トランザクションモードも有効にする必要がある 参考資料は https://fractaledmind.github.io/2024/04/… と https://kerkour.com/sqlite-for-servers および https://www.sqlite.org/lang_transaction.html#immediate で、2 つ目のリンクのSQLITE_BUSYの部分と 3 つ目のリンクで詳しい内容を確認できるPR、Issue、Gist に詳細が非常によく整理された印象的なアップグレードだ。この変更で サイト検索のような機能差 が生じたのか、現在の SQLite ファイルのディスクサイズはいくつなのか気になる
SQLite ファイルのサイズはどこかで 3.8GB と言っていたように記憶している