- 更新日: 2016年7月21日
- Rails
Rails ActiveRecordをマルチスレッドで使う
マルチスレッド環境で Rails の ActiveRecord を使おうとしたところ、以下のエラーに遭遇しました。スレッド毎に、ActiveRecord がDB接続(コネクション)を確保しようとするために、コネクションのプールサイズをオーバーしてしまうのが原因です。
1 2 3 4 |
ActiveRecord::ConnectionTimeoutError in SomeController#some_action could not obtain a database connection within 5.000 seconds (waited 5.003 seconds) |
— 環境 —
Rails 4.2
Ruby 2.3
エラーが発生したコード
スレッド毎に User モデル(データベースの users テーブル)を検索して、結果を後で user_groups としてまとめるようなコードを書いていた。
1 2 3 4 5 6 7 8 9 10 11 |
threads = [] threads.push(Thread.new do first_group = User.first_group end) threads.push(Thread.new do second_group = User.second_group end) threads.each {|t| t.join(30)} user_groups = first_group + second_group |
User.first_group, User.second_group は、それぞれ特定の条件で User モデルを検索する scope です。この書き方だと ActiveRecord::ConnectionTimeoutError が発生します。
ActiveRecord::Base.connection_pool.with_connection を使う
ActiveRecord::Base.connection_pool.with_connection を使って、そのブロック内でモデルを検索するようにコードを修正することで、ActiveRecord::ConnectionTimeoutError を解決できます。
ActiveRecord::Base.connection_pool.with_connection のソースを読んだところ、fresh_connection(新規のDBコネクション)の場合、ブロック実行後に ensure 節で release_connection(コネクションを解放)するコードとなっています。次の説明が書いてありました。
Use ActiveRecord::Base.connection_pool.with_connection(&block), which obtains a connection, yields it as the sole argument to the block, and returns it to the pool after the block completes.
意訳)ActiveRecord::Base.connection_pool.with_connection(&block) を使うと、コネクションを取得して、そのコネクションをブロック引数としてブロックを実行し、ブロック実行が終了したらそのコネクションをプールに戻します。
ということで、ActiveRecord::Base.connection_pool.with_connection に渡すブロック内で、User モデルを検索するようにコードを変更しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
threads = [] threads.push(Thread.new do ActiveRecord::Base.connection_pool.with_connection do first_group = User.first_group end end) threads.push(Thread.new do ActiveRecord::Base.connection_pool.with_connection do second_group = User.second_group end end) threads.each {|t| t.join(30)} user_groups = first_group + second_group |
これでエラーが発生しなくなりました。
- – 参考リンク –
- ActiveRecordを複数スレッド環境で利用する recompile.net
- Rubyでマルチスレッド・マルチプロセスのプログラムを書くならParallelが便利 – ワシはワシが育てる
- [Ruby] 例えば、ActiveRecord の connection_pool を止める – sonots:blog
- ActiveRecordのconnection_poolで怒られる – PartyIX
- SinatraでActiveRecordを使ってるとしょっちゅうTimeOutで固まる件 – 今日のなんでやねん(2014-01-02)
- Rails の関連記事
- RailsでMySQLパーティショニングのマイグレーション
- Rails ActiveRecordでdatetime型カラムのGROUP BY集計にタイムゾーンを考慮する
- RailsプラグインGemの作成方法、RSpecテストまで含めたrails pluginの作り方
- RailsでAMPに対応するgemをリリースしました
- Railsでrequest.urlとrequest.original_urlの違い
- Railsでwheneverによるcronバッチ処理
- Google AnalyticsのRails Turbolinks対応
- Railsアプリにソーシャル・シェアボタンを簡単設置
- Rails監視ツール用にErrbitをHerokuで運用
- Facebook APIバージョンのアップグレード手順(Rails OmniAuth)
- 初回公開日: 2016年7月19日
Leave Your Message!