ActiveJobに大量に仕事を依頼するようにしたら、以下のようなログが出るようになりました。
could not obtain a database connection within 5.000 seconds
早速調査。
ActiveJobでsidekiqを使う場合、connection_poolの値はconcurrency + 1以上にしよう - repl.info
つまりは、ActiveJobがDBへのコネクションプールの数以上に起動して、コネクションプールが空くのを待っている間にタイムアウトしてしまう、というやつです。 そして、sidekiq起動時にDBに接続を試みるので、コネクションプールは並列数+1にしましょうということでした。
ActiveJobは、アダプタにsidekiqを使っています。
修正前
特にパラメータの指定もなく、以下のように起動。
bundle exec sidekiq
問題点
デフォルトだと、sidekiqの並列数(concurrency)は10です。しかし、DBへのコネクションプールの設定はというと…。database.ymlを見てみると…。
default: &default adapter: postgresql encoding: unicode username: <%= ENV.fetch("DOCKER_POSTGRES_USER") { "postgres" } %> port: <%= ENV.fetch("DOCKER_POSTGRES_PORT") { 5432 } %> pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> # <= ここ! host: localhost development: <<: *default database: example_development test: <<: *default database: example_test production: <<: *default database: example_production
特に指定がなければ5になっています。並列数10に対してコネクションプールが5なのでそりゃ落ちますわ…。
修正していく
ということで、先の参照先のリンクの情報を元に、並列数が10の場合はコネクションプールを11にすればOK。
env RAILS_MAX_THREADS=11 bundle exec sidekiq -c 10
これで大量にActiveJobでキューイングしてみたところ、問題なく全ての処理が終了することを確認。
Herokuに対応する
本番環境はHerokuなので、herokuの記事も参考にしました。
環境変数 SIDEKIQ_RAILS_MAX_THREADS
と SIDEKIQ_CONCURRENCY
を定義して、それで調整します。
接続数の上限を意識する
heroku-postgresのstandard-0を使っている場合は、最大接続数は100なので、結構大きなアプリケーションでなければ使い切ることはありません。しかし、hobby-devやhobby-basicの場合の最大接続数は20です。
今のdatabase.ymlのままだと、WebDynoはpumaのworker毎に5の接続数を使うので、worker数を2と想定した場合、残りの接続数は10。
じゃあWorkerDynoのsidekiqは残りの10を使っていいのか?というと、heroku run rails console
で接続することもあるし、heroku schedulerを使うこともあり得るので、3〜4接続数くらいは残しておかないと詰みます。
そのため、sidekiqの並列数は5として、コネクションプールは6とします。そうすると、接続数が4つ残る計算に。
worker: RAILS_MAX_THREADS=${SIDEKIQ_RAILS_MAX_THREADS:-6} bundle exec sidekiq -c ${SIDEKIQ_CONCURRENCY:-5}
これで、herokuに作ったステージング環境で動作検証したところ、並列数を5に制御しているため、エラー起きずにちゃんと処理が終わりました。 接続数とコネクションプールの関係を考えると、デフォルトにしておくのは危険なので設定しておきましょう!