- この1年間、SQLite を使って Rails アプリケーションを高性能かつ安定して動かす方法を深く理解しようと努めてきた
- その過程でさまざまな教訓を得たため、これを共有したい
- 問題の原因と解決方法を説明する
SQLite と Rails の問題点
- 基本的に、SQLite を使った Rails アプリケーションはそのまますぐに使える状態ではない
- 多少の調整とチューニングによって、高性能で安定したアプリケーションを作れる
- Rails 8 では、デフォルト設定だけで本番運用の準備が整った状態になることを目標にしている
デモアプリケーション「Lorem News」
- 「Lorem News」というデモアプリケーションを使って、問題と解決策を説明する
- このアプリケーションは Hacker News のクローンで、ユーザーは投稿とコメントを作成できる
パフォーマンステスト
oha 負荷テスト CLI とアプリケーション内のベンチマーク用パスを使って性能をテストする
- 単一リクエストと同時リクエストを通じて性能を測定する
主な問題: SQLITE_BUSY 例外
- SQLite は一度に1つの書き込み処理しか許可しないよう、書き込みロックを使用する
- 複数の接続が同時に書き込みロックを試みると
SQLITE_BUSY 例外が発生する
- この問題を解決するには、即時トランザクションを使う必要がある
即時トランザクション
- デフォルトでは、SQLite は遅延トランザクションモードを使用する
- 即時トランザクションを使うと書き込みロックをただちに試行し、失敗した場合は再試行できる
sqlite3-ruby gem を使って、デフォルトのトランザクションモードを即時モードに設定できる
タイムアウト設定
database.yml ファイルでタイムアウトを設定することで、SQLITE_BUSY 例外を減らせる
- SQLite の
busy_timeout 設定を使って書き込みロックを再試行できる
GVL(グローバル VM ロック)の問題
sqlite3-ruby gem は SQLite の C コードを呼び出す際に GVL を解放しない
- これが同時実行性能を低下させる
busy_handler を使って GVL を解放し、性能を改善できる
busy_timeout の再実装
busy_timeout を再実装し、すべてのクエリが同じ頻度で再試行されるように設定する
- これにより、古いクエリがタイムアウトしないようにする
パフォーマンス改善
- パフォーマンスを改善するために、次の設定を適用する必要がある
- 即時トランザクションを使用
- タイムアウトを設定
busy_handler を使用
- WAL(Write-Ahead Logging)モードを使用
- 読み取り/書き込みの接続プールを分離
GN⁺ のまとめ
- SQLite を使った Rails アプリケーションの性能問題と解決策を扱っている
- 即時トランザクション、タイムアウト設定、GVL の解放、WAL モードの使用、読み取り/書き込み接続プールの分離といった方法で性能を改善できる
- この記事は SQLite と Rails を使う開発者にとって非常に有用だ
- 類似の機能を持つ別のプロジェクトとしては PostgreSQL と MySQL を勧める
1件のコメント
Hacker Newsのコメント
OldmoeのLitestackプロジェクトの紹介
詳細な記事への感謝
SQLite関連の作業をする人へのおすすめ
FOSS分析システムについての質問
sqlite3-ruby gemのGVL問題
個人Webサービスの設定
PRAGMA journal_mode = WALPRAGMA busy_timeout = 5000PRAGMA synchronous = NORMALPRAGMA cache_size = 1000000000PRAGMA foreign_keys = truePRAGMA temp_store = memoryBEGIN IMMEDIATEトランザクションを使うDjangoについての質問
busy_timeoutのデフォルト設定への疑問busy_timeoutメソッドが、古いクエリを罰するような遅延を持つ理由が気になるSQLiteとRailsの利用についての意見
Rails統合の問題解決への感謝