Railsのエラー『ActiveRecord::ConnectionTimeoutError: could not obtain a database connection』の解決方法

Railsのエラー『ActiveRecord::ConnectionTimeoutError: could not obtain a database connection』の解決方法

Railsアプリで「ActiveRecord::ConnectionTimeoutError: could not obtain a database connection」というエラーが発生する原因と解決策について解説する。このエラーは、データベースの接続プールが枯渇したときに発生する。発生条件や調査方法、具体的な対応策をコード付きで説明する。

エラーの発生条件

このエラーは、以下のような状況で発生する。

  • 同時に多数のデータベース接続が発生し、接続プールの上限を超えた
  • config/database.ymlpool 設定が不足している
  • データベースの接続が適切にクローズされていない
  • マルチスレッドまたはマルチプロセスのアプリケーションで接続プールを適切に設定していない
  • ワーカー(Sidekiq/Pumaなど)が過剰に動作している

エラーの発生例

Railsアプリケーションで以下のようなエラーメッセージが表示される。

ActiveRecord::ConnectionTimeoutError: could not obtain a database connection within 5 seconds.

接続プールの設定を確認

config/database.ymlpool の設定を確認する。

production:
  adapter: postgresql
  encoding: unicode
  pool: 5  # この値を増やす
  timeout: 5000

デフォルトの pool 値は 5 だが、アプリの負荷に応じて増やす。

接続数の確認

データベースの接続数を確認する。

PostgreSQL の場合:

SELECT datname, numbackends FROM pg_stat_database;

MySQL の場合:

SHOW STATUS WHERE variable_name = 'Threads_connected';

これらのコマンドで、現在の接続数が pool の設定を超えているか確認する。

データベース接続を適切にクローズ

長時間保持されるデータベース接続があると、接続数が枯渇する。使用後に適切に ActiveRecord::Base.connection_pool.release_connection を実行する。

ActiveRecord::Base.connection_pool.release_connection

または、ブロック内で確実に接続を閉じる。

ActiveRecord::Base.connection_pool.with_connection do |conn|
  conn.execute("SELECT 1")
end

Sidekiqの並列ワーカー数を調整

Sidekiq の concurrency を制限する。

# config/sidekiq.yml
:concurrency: 10

ワーカー数が多すぎると、データベース接続数を圧迫するため、適切な数に調整する。

Pumaのスレッド数を調整

Pumaのスレッド数も調整が必要。

# config/puma.rb
workers Integer(ENV.fetch("WEB_CONCURRENCY") { 2 })
threads_count = Integer(ENV.fetch("RAILS_MAX_THREADS") { 5 })
threads threads_count, threads_count

スレッド数を増やしすぎるとデータベース接続が不足する可能性がある。

データベースの最大接続数を増やす

PostgreSQL の最大接続数を増やすには postgresql.conf を変更する。

max_connections = 200

MySQL の場合:

SET GLOBAL max_connections = 200;

本番環境では、サーバーのリソースを考慮しながら適切な値を設定する。

不要なデータベース接続を削除

Rails コンソールで未使用の接続を手動でクローズする。

ActiveRecord::Base.clear_active_connections!

または、rake コマンドで一括リセット。

rails db:clear_active_connections!

データベースのクエリを最適化

過剰なクエリがデータベース負荷を増やす原因となる。

  • N+1 問題 を回避するために includes を使う。
  • index を適切に追加する。
  • pluckselect を活用し、不要なデータ取得を減らす。

まとめ

「ActiveRecord::ConnectionTimeoutError」はデータベース接続プールが枯渇したときに発生する。適切な pool 設定、接続の適切な管理、ワーカー数の調整、データベースの最適化によって回避できる。