patorashのブログ

方向性はまだない

sidekiqでDBへのコネクションプールを使い切らないようにする

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の記事も参考にしました。

devcenter.heroku.com

環境変数 SIDEKIQ_RAILS_MAX_THREADSSIDEKIQ_CONCURRENCY を定義して、それで調整します。

接続数の上限を意識する

heroku-postgresのstandard-0を使っている場合は、最大接続数は100なので、結構大きなアプリケーションでなければ使い切ることはありません。しかし、hobby-devやhobby-basicの場合の最大接続数は20です。

今のdatabase.ymlのままだと、WebDynoはpumaのworker毎に5の接続数を使うので、worker数を2と想定した場合、残りの接続数は10。 じゃあWorkerDynoのresqueは残りの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に制御しているため、エラー起きずにちゃんと処理が終わりました。 接続数とコネクションプールの関係を考えると、デフォルトにしておくのは危険なので設定しておきましょう!