Resque プールを使用したワーカー使用率の最大化
最終更新日 2017年11月15日(水)
Table of Contents
Heroku では、動作可能なユニット (Web サーバーや一部のワーカーコードなど) を dyno 上で実行することによってアプリケーションを実行します。これらの dyno のリソースは固定です。詳しく調査しなくても利用可能なリソースが最大限に活用され、アプリケーションで最大のパフォーマンスを達成できることは、アプリケーションの所有者にとって有益です。
Heroku では、dyno ごとに複数のリクエストを効率的に処理できるように、並列 Web サーバーの使用を推奨しています。同様に、1 つの Worker dyno で一度に複数のジョブを処理できるワーカーバックエンドの使用を推奨しています。
現在、1 つの Worker dyno で一度に複数の並列ジョブを実行するためのオプションはいくつかあります。Sidekiq は一般的なオプションの 1 つですが、スレッドを使用するため、コードがスレッドセーフであることが条件となります。アプリケーションがスレッドセーフであることが確実でない場合は、プロセスを使用した方が安全です。
resque-pool gem は、dyno あたり複数のワーカーを実行することによって resque ベースのワーカーをスケールアウトするので、適しています。resque-pool gem は、その設計方式において Unicorn Web サーバーと同じように動作します。1 つのマスタープロセスを起動した後、UNIX の fork コマンドを使用して、すべてのプロセスがデータを個別に処理できる複数の resque ワーカーを作成します。これら複数の resque ワーカーにより、ワーカーの “プール” が作成されます。Ruby では並列性のためにプロセスを使用する方が安全ですが、メモリオーバーヘッドが増加する可能性があります。
物理 RAM の容量は dyno タイプによって異なります。 この制限の範囲内で、dyno あたりのワーカー実行数をできるだけ多くしてください。制限を超えると、dyno でディスクへのスワップが始まり、ワーカーが大幅に低速化します。
インストール
Resque を使用するプロジェクトがすでにある前提で、まず resque-pool gem を Gemfile に追加する必要があります。
gem 'resque-pool'
その後、bundle install
を実行します。
シグナル設定
期待するシグナル処理を Heroku から得るためには、TERM_CHILD
環境変数を 1
に設定する必要があります。これは、設定で次のようにして実行できます。
$ heroku config:set TERM_CHILD=1
この設定についての詳細は、resque-pool の readme、およびこちらの resque に関するブログ記事を参照してください。
ワーカー設定
マシンに resque-pool がインストールされたら、キューあたりのワーカー実行数を resque-pool に指示する YAML ファイルが必要になります。このファイルは config/resque-pool.yml
にあります。send_welcome_email
というジョブがあり、そのキューで 5 つのワーカーを実行するとします。また、crunch_data
という別のジョブがあり、そのキュー専用に 10 個のワーカーが必要であるとします。この場合、config/resque-pool.yml
は次のようになります。
---
send_welcome_email: 5
crunch_data: 10
すべてのキューから処理するワーカーのグループを指定する場合は、アスタリスク *
を使用できます。
resque-pool 0.4.0(beta) 以上を使用している場合、resque が使用しているワーカーの数を変更するためにデプロイする必要がないよう、YAML ファイルで環境変数を使用することができます。すべてのキューを処理するワーカーの総数を設定する場合は、次のようにして実行できます。
initializers/resque-pool.rb
に次のコードを追加します。
WORKER_CONCURRENCY = Integer(ENV["WORKER_CONCURRENCY"] || 5)
config/resque-pool.yml
に次の YAML を追加します。
---
'*': <%= WORKER_CONCURRENCY %>
resque-pool 0.4.0(beta) 以上では、ERB が正しく評価され、Heroku 設定での指定どおりにワーカーが起動されます。
resque-pool 0.3.0 以下を使用している場合、ERB はサポートされていません。環境変数を動的に追加するために、アプリが起動するたびに設定ファイルをプログラムによって書き込むことができます。initializers/resque-pool.rb
に次の内容を追加します。
WORKER_CONCURRENCY = Integer(ENV["WORKER_CONCURRENCY"] || 5)
RESQUE_POOL_CONFIG = {"*" => WORKER_CONCURRENCY}
File.open(Rails.root.join('config/resque-pool.yml'), 'w') {|f| f.write RESQUE_POOL_CONFIG.to_yaml }
これで、Rails アプリが作成されるたびに新しい YAML ファイルが生成されるようになります。グループあたりのワーカー数を変更するには、RESQUE_POOL_CONFIG
ハッシュを変更できます。
プールのセットアップ
データベースへの接続など、アプリケーションで何らかの接続を使用している場合、resque プールのワーカーがフォークした後は、必ず切断して再接続する必要があります。Rakefile
で次のコードを使用して、これを確実に実行できます。
# this task will get called before resque:pool:setup
# and preload the rails environment in the pool manager
task "resque:pool:setup" do
# close any sockets or files in pool manager
ActiveRecord::Base.connection.disconnect!
Resque::Pool.after_prefork do
ActiveRecord::Base.establish_connection
end
end
開始
実行する Resque ワーカーの数をイニシャライザで宣言したので、次を実行してワーカーをローカルで開始できます。
$ bundle exec resque-pool
本番環境では、Procfile
に次の行が必要になります。
worker: bundle exec resque-pool
ワーカーのスケールアップ
Resque プールで Resque ワーカーをいくつ実行できるでしょうか。この数は、アプリのサイズと、実行している dyno のサイズの関数です。ワーカーが増えるたびにメモリ消費と処理能力が増加します。ランタイムメトリクスラボ機能を使用して、resque-pool を実行しているアクティブな Worker dyno が消費しているメモリの量を調べることをお勧めします。使用している dyno の最大サイズよりもメモリの量が少ない場合は、WORKER_CONCURRENCY
を増やします。徐々に増やしますが、アプリがメモリの制限を超えないようにしてください。制限を超えると、アプリでスワップが始まって Worker dyno が大幅に低速化します。
懸念事項
複数のプロセスを dyno で実行するときの主な懸念事項は、プロセスの実行継続 (キープアライブ) です。Heroku では、Procfile
で直接宣言されたプロセスのみを監視できます。このプロセスがクラッシュした場合、そのことを検出してプロセスを再開できます。ただし、そのプロセスが起動する子プロセスがクラッシュした場合に、そのことを検出したり、子プロセスを再開したりする手段はありません。
このため、どのようなマルチプロセスプログラムでも、プログラム自体を適切に管理することが重要です。そうすれば、resque-pool により、クラッシュしたプロセスが再開されて追加のプロセスが得られることが保証されます。とはいえ、ゾンビプロセスや、クラッシュと再開を繰り返すプロセスなど、対応が困難な特殊なケースの可能性はあります。
resque-pool に移行するときは、本番環境に配置する前にアプリケーションでの動作をきめ細かく監視できるよう、まずローカルで移行してから、ステージングサーバー上で移行することをお勧めします。