1 ポイント 投稿者 GN⁺ 2024-09-13 | 1件のコメント | WhatsAppで共有
  • この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件のコメント

 
GN⁺ 2024-09-13
Hacker Newsのコメント
  • OldmoeのLitestackプロジェクトの紹介

    • SQLiteとRailsを使う人は、OldmoeのLitestackプロジェクトをチェックするべき
    • Litestackは、SQLiteの強力さを活用してWebアプリケーションのデータインフラを提供するRuby gem
    • SQLデータベース、高速キャッシュ、強力なジョブキュー、信頼できるメッセージブローカー、全文検索エンジン、メトリクスプラットフォームを1つのパッケージで提供する
    • 現在のプロジェクトで使っており、とても満足している
  • 詳細な記事への感謝

    • SQLiteのWebアプリケーションをスケールさせようとしている人にとって有用な情報
    • Railsを超えて、ほかのフレームワークにも適用できる
    • 著者に感謝する
  • SQLite関連の作業をする人へのおすすめ

    • 使っている言語やフレームワークに関係なく、SQLite関連の作業をしている人はこの記事を読むべき
    • 数年前なら自分で解決しなければならなかった問題を扱っている
    • 著者に感謝する
  • FOSS分析システムについての質問

    • セットアップが簡単なFOSS分析システムを作っている
    • イベントデータを別のSQLiteデータベースに送って、メインアプリのデータと分離しようとしている
    • 1秒あたり1000件以上のイベントを処理できるスケーラビリティに懸念がある
    • サーバーメモリにイベントを保存し、毎秒1回まとめて書き込む方法を検討している
    • SQLiteの多くのDB書き込み問題を解決できる妥当な方法かどうか意見を求めている
  • sqlite3-ruby gemのGVL問題

    • sqlite3-ruby gemは、SQLite呼び出し時にGVLを解放しない
    • これはたいてい合理的な判断に見える
    • Python拡張では別のやり方で設計されている可能性がある
    • extralite gemはブロッキング中にGVLを解放し、一般的により高速で、並行性の問題もない
  • 個人Webサービスの設定

    • 個人Webサービスで使っているいくつかの設定:
      • PRAGMA journal_mode = WAL
      • PRAGMA busy_timeout = 5000
      • PRAGMA synchronous = NORMAL
      • PRAGMA cache_size = 1000000000
      • PRAGMA foreign_keys = true
      • PRAGMA temp_store = memory
      • BEGIN IMMEDIATEトランザクションを使う
  • Djangoについての質問

    • この記事は素晴らしい
    • Django向けの似たような解決策があるのか気になる
    • ArchiveBoxはDjango経由でSQLiteを使っており、Railsで言及されている問題によく直面する
    • アプリの別チャネルを通じてすべての書き込みを直列化しなくてもよいSQLiteレイヤーの解決策があるとよい
  • busy_timeoutのデフォルト設定への疑問

    • とても有益で、よく書かれた記事
    • デフォルトのbusy_timeoutメソッドが、古いクエリを罰するような遅延を持つ理由が気になる
    • なぜこれがデフォルト設定として意味を持つのか気になる
  • SQLiteとRailsの利用についての意見

    • SQLiteとRailsは好きだが、これは本番環境でMS Accessを使うのに似ている
  • Rails統合の問題解決への感謝

    • 統合の問題を解決し、ほかの人を助けてくれるのはいつもうれしい
    • こうした修正がRailsのデフォルト設定に含まれることを願っている
    • Railsアプリを運用しており、数年前にPostgresへ移行して非常に満足している
    • それでも代替手段があるのは良いことで、別の作業にはSQLiteを使っている