9

Heroku では、2 つの Web dyno と 1 つのワーカー dyno の両方で Rails アプリを実行しています。Sidekiq で 1 日中何千ものワーカー タスクを実行していますが、ときどき ActiveRecord::ConnectionTimeoutError が発生します (1 日に約 50 回)。次のようにユニコーンサーバーをセットアップしました

worker_processes 4
timeout 30
preload_app true

before_fork do |server, worker|
    # As suggested here: https://devcenter.heroku.com/articles/rails-unicorn
    Signal.trap 'TERM' do
        puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
        Process.kill 'QUIT', Process.pid
    end

    if defined?(ActiveRecord::Base)
        ActiveRecord::Base.connection.disconnect!
    end
end

after_fork do |server,worker|
    if defined?(ActiveRecord::Base)
        config = Rails.application.config.database_configuration[Rails.env]
        config['reaping_frequency'] = ENV['DB_REAP_FREQ'] || 10 # seconds
        config['pool']            = ENV['DB_POOL'] || 10
        ActiveRecord::Base.establish_connection(config)
    end

    Sidekiq.configure_client do |config|
        config.redis = { :size => 1 }
    end

    Sidekiq.configure_server do |config|
        config = Rails.application.config.database_configuration[Rails.env]
        config['reaping_frequency'] = ENV['DB_REAP_FREQ'] || 10 # seconds
        config['pool']            = ENV['DB_POOL'] || 10
        ActiveRecord::Base.establish_connection(config)
    end
end

Heroku では、Heroku の推奨に従って DB_POOL 構成変数を 2 に設定しました。これらのエラーはまったく発生する必要がありますか? そのようなエラーを回避することが不可能であるというのは奇妙に思えますよね? 何を提案しますか?

4

2 に答える 2

14

sidekiq サーバー (遅延したタスクを実際に実行しているサーバー上で実行されているプロセス) は、デフォルトで最大 25 のスレッドにダイヤルして、そのキューから作業を処理します。これらの各スレッドは、タスクで必要な場合、ActiveRecord を介してプライマリ データベースへの接続を要求している可能性があります。

接続プールが 5 つの接続しかなく、25 のスレッドが接続しようとしている場合、5 秒後にプールから使用可能な接続を取得できない場合、スレッドは放棄され、接続タイムアウトが発生します。エラー。

Sidekiq サーバーのプール サイズを同時実行レベルに近い値に設定すると (-cプロセスの開始時にフラグで設定)、データベースへの接続をさらに多く開くという犠牲を払って、この問題を軽減することができます。たとえば、Heroku で Postgres を使用している場合、プランによっては 20 に制限されているものもあれば、接続制限が 500 に制限されているものもあります ( source )。

Unicorn のようなマルチプロセス サーバー環境を実行している場合は、フォークされた各プロセスが行う接続数も監視する必要があります。ユニコーン プロセスが 4 つあり、デフォルトの接続プール サイズが 5 の場合、ユニコーン環境は常に 20 のライブ接続を持つことができます。詳細については、Heroku のドキュメントを参照してください。また、DB プールのサイズは、各 dyno が開いている接続数が多くなることを意味するのではなく、新しい接続が必要な場合に、最大でその数が作成されるまで作成されることに注意してください。

そうは言っても、ここに私がしていることがあります。

# config/initializers/unicorn.rb

if ENV['RACK_ENV'] == 'development'
  worker_processes 1
  listen "#{ENV['BOXEN_SOCKET_DIR']}/rails_app"
  timeout 120
else
  worker_processes Integer(ENV["WEB_CONCURRENCY"] || 2)
  timeout 29
end

# The timeout mechanism in Unicorn is an extreme solution that should be avoided whenever possible. 
# It will help catch bugs in your application where and when your application forgets to use timeouts,
# but it is expensive as it kills and respawns a worker process.
# see http://unicorn.bogomips.org/Application_Timeouts.html

# Heroku recommends a timeout of 15 seconds. With a 15 second timeout, the master process will send a 
# SIGKILL to the worker process if processing a request takes longer than 15 seconds. This will 
# generate a H13 error code and you’ll see it in your logs. Note, this will not generate any stacktraces 
# to assist in debugging. Using Rack::Timeout, we can get a stacktrace in the logs that can be used for
# future debugging, so we set that value to something less than this one

preload_app true # for new relic

before_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
    Process.kill 'QUIT', Process.pid
  end

  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.connection.disconnect!
  end

end

after_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to sent QUIT'
  end

  Rails.logger.info("Done forking unicorn processes")

  #https://devcenter.heroku.com/articles/concurrency-and-database-connections
  if defined?(ActiveRecord::Base)

    db_pool_size = if ENV["DB_POOL"]
      ENV["DB_POOL"]
    else
      ENV["WEB_CONCURRENCY"] || 2
    end

    config = Rails.application.config.database_configuration[Rails.env]
    config['reaping_frequency'] = ENV['DB_REAP_FREQ'] || 10 # seconds
    config['pool']              = ENV['DB_POOL'] || 2
    ActiveRecord::Base.establish_connection(config)

    # Turning synchronous_commit off can be a useful alternative when performance is more important than exact certainty about the durability of a transaction
    ActiveRecord::Base.connection.execute "update pg_settings set setting='off' where name = 'synchronous_commit';"    

    Rails.logger.info("Connection pool size for unicorn is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}")
  end

end

そしてsidekiqの場合:

# config/initializers/sidekiq.rb

Sidekiq.configure_server do |config|

  sidekiq_pool = ENV['SIDEKIQ_DB_POOL'] || 20

  if defined?(ActiveRecord::Base)
    Rails.logger.debug("Setting custom connection pool size of #{sidekiq_pool} for Sidekiq Server")
    db_config = Rails.application.config.database_configuration[Rails.env]
    db_config['reaping_frequency'] = ENV['DB_REAP_FREQ'] || 10 # seconds
    cb_config['pool']              = sidekiq_pool
    ActiveRecord::Base.establish_connection(db_config)

    Rails.logger.info("Connection pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}")
  end
end

すべてがうまくいけば、プロセスを起動すると、ログに次のようなものが表示されます。

Setting custom connection pool size of 10 for Sidekiq Server
Connection pool size for Sidekiq Server is now: 20
Done forking unicorn processes
   (1.4ms)  update pg_settings set setting='off' where name = 'synchronous_commit';
Connection pool size for unicorn is now: 2

ソース:

于 2013-09-21T00:58:59.473 に答える
0

Sidekiq サーバー構成ではdb_pool、同時実行数と同じ数を使用することをお勧めします。これは 2 より大きい値に設定されていると想定しています。

db_poolあなたの設定が機能していると仮定するとunicorn.rb(私はこの方法でそれを行った経験がありません)、潜在的な解決策は別の環境変数を設定してSidekiqdb_poolを直接制御することです。

sidekiq の同時実行数が 20 の場合、次のようになります。

構成変数 -SIDEKIQ_DB_POOL = 20

Sidekiq.configure_server do |config|
  config = Rails.application.config.database_configuration[Rails.env]
  config['reaping_frequency'] = ENV['DB_REAP_FREQ'] || 10 # seconds
  config['pool']            = ENV['SIDEKIQ_DB_POOL'] || 10
  ActiveRecord::Base.establish_connection(config)
end

DB_POOLこれにより、Web ワーカーとバックグラウンド ワーカーのいずれかに最適化された 2 つの個別のプールが確保されます。SIDEKIQ_DB_POOL

于 2013-09-20T13:21:19.820 に答える