-
Notifications
You must be signed in to change notification settings - Fork 9
Rails unicorn
プロセスが並列にリクエストを投げるようなウェブアプリケーションは、同じ時間に一つだけしかリクエストを投げないアプリケーションに比べて、Dynoのリソースをより効率的に使える状態になります。従って、並列リクエスト処理は本格的なサービスを開発、運用するいかなる場合でも推奨されます。
Railsは元々は同時に1リクエストしか処理しないように設計をされています。そして、徐々にこの設計から、単体のRubyプロセス内でリクエストの並列処理を許すようなスレッドセーフの実装へと移行しています。しかし今日では、ほとんどのRubyアプリケーションはサポートをしていません。
Unicornウェブサーバは、一つのDynoで複数のRubyプロセスを走らせる事で、どんなRailsアプリケーションでも並列に走らせられるようになります。
このガイドでは、Unicornウェブサーバを使ったHerokuへのRailsアプリケーションのデプロイを見て行きます。基本的なRailsのセットアップはGetting Started with Railsをご覧ください。
製品としてあなたのアプリをデプロイする前に、いつもステージング環境で新しくデプロイされた物をテストしてください。
Unicornは入力されるリクエストを同時に制御するために分岐されたプロセスを使うRack HTTP サーバです。
始めに、ユニコーンをあなたのアプリのGemfileに追加します。
:::ruby
gem 'unicorn'
ローカルでbundleを設定するため、bundle install
を実行します。
Unicornのための設定ファイルをconfig/unicorn.rb
、もしくは任意のパスに作ります。単純なRailsアプリケーションならば、私たちは以下の基本的な設定をおすすめします。:
:::ruby
# config/unicorn.rb
worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3)
timeout 15
preload_app true
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
defined?(ActiveRecord::Base) and
ActiveRecord::Base.connection.disconnect!
end
after_fork do |server, worker|
Signal.trap 'TERM' do
puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT'
end
defined?(ActiveRecord::Base) and
ActiveRecord::Base.establish_connection
end
これは、監視用にNew Relicを使っている、ActiveRecordを使った標準的なRailsアプリを前提としています。他の利用可能な設定項目についての情報は、Unicornのドキュメントをご覧ください。
:::ruby
worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3)
Unicornは、Railsアプリがスレッドセーフであるかどうかを考えずに、複数の並列リクエストをサポートすることが出来るように、それぞれのDynoの中で複数のOSプロセスへ分岐させます。Unicornの専門用語でこれらはワーカープロセスとして言及されますので、Dynoの中で走っているHerokuのワーカープロセスと混同しないようにしてください。
それぞれの分岐されたOSプロセスは更なるメモリを消費します。これはあなたが一つのDynoでどれだけのプロセスを実行できるかを制限します。典型的なRailsのメモリ使用量からすると、2~4つのUnicornのワーカープロセスが実行できると予想されます。あなたのアプリケーションは多かれ少なかれ、具体的なメモリ使用量によって処理を許可するでしょう。そして私たちはアプリケーションの調整を早くするために、この数字を許可するための設定変数として明示しておくことをおすすめします。heroku logs
やログのアドオンのどれかを使ってR14エラー(メモリ割当超過)をあなたのアプリのログから監視してください。
:::ruby
preload_app true
# ...
before_fork do |server, worker|
# ...
defined?(ActiveRecord::Base) and
ActiveRecord::Base.connection.disconnect!
end
after_fork do |server, worker|
# ...
defined?(ActiveRecord::Base) and
ActiveRecord::Base.establish_connection
end
あなたのアプリケーションを事前に読み込んでおく事は、ここのUnicornのワーカープロセスの立ち上がり時間を減らし、さらにbefore_fork
とafter_fork
の呼び出しを使用して個々のワーカーそれぞれの外部接続を管理できるようになります。上記の設定では、ここのワーカープロセスが正しくpostgresとの接続を確保できるように、これらの呼び出しが使われています。
New Relicもまた、Unicornを使ったアプリの正確なデータ収集のためにpreload_app true
を勧めています。preload_app true
を使わないでNew Relicを利用する際の情報は、彼らのドキュメントを参照してください。
:::ruby
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
# ...
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
# ...
end
POSIX Signalsは現在のイベントや状態の変化を示すためのプロセス間通信の形です。伝統的にQUIT
はすぐにプロセスを終了し、コアダンプを生成するために使われます。TERM
はプロセスを終了するのに使われますが、プロセスがその後に自身をきれいにすることを許可します。
Unicornはグレースフルシャットダウンを判断するためにQUIT
を使用します。マスタープロセスがこの信号を受信したときに、そのときに送信中のリクエストを完了してから適切に終了するだろう全てのワーカーに対してQUIT
を送ります。ワーカープロセスが終了した後、マスタープロセスも終了します。
HerokuはTERM
を、終了しようとしているDynoの中の全てのプロセスを判断するために使用しています。上記の設定は、このTERM
がUnicornのモデル(ワーカーが閉じ込められ、信号を無視する)に適切に変換されているかを確かめています。マスターは自身でQUIT
を閉じ込め、送ることでグレースフルシャットダウンのプロセスを始めます。
Herokuは適切な終了のためプロセスに10秒間を与え、このあとKILL
を強制的に終了させるために全てのプロセスに送ります。もし、あるリクエストが10秒以上かかった場合、それは中断させられるでしょう。適切な終了が出来なかった場合を検知するためにあなたのアプリケーションに書かれていることに目を光らせてください。
Herokuのルータはリクエストタイムアウトがある前に30秒の猶予を与えます。ルータを通してDynoへリクエストが届けられたあと、レスポンスを返すか、ルータがカスタム可能なエラーページを返すまでに30秒あります。これはリソースをあげようとして、リクエストが滞ってしまうのを防ぐために行われています。ルータがクライアントにレスポンスを返すだろう間は、クライアントがレスポンスを受け取っていたとしても、Unicornのワーカーがリクエストを処理しつづけてくれます。これは、おそらくリクエストが滞ったことでワーカーが忙しくなってしまったことを意味します。あなたのアプリケーションのリクエストがリクエストタイムアウトを越えて、あなたのDynoを忙しくさせないようにするために、私たちはRack::TimeoutとUnicornのタイムアウト設定の両方を使う事をおすすめします。
Unicornには調整可能なタイムアウト設定があります。タイムアウトは、Unicornがあなたのアプリによってリクエストが処理され始めたときにカウントダウンが始まり、レスポンスが帰るときに終わります。もしリクエストが決まった時間よりも長い時間を取っている場合、マスターはリクエストを持つワーカーにSIGKILLします。
:::ruby
timeout 15
私たちは15秒のタイム会うとを推奨します。15秒のタイムアウトを設定していると、マスタープロセスはリクエストの処理に15秒以上時間がかかっているワーカに対してSIGKILL
を送ります。この場合はH13エラーを生成し、ログの中で確認することができます。注意すべき点として、これはデバッグを支援するためのスタックトレースは生成しません。
Rack::Timeoutの限界に当たった場合、リクエストは閉じられ、スタックとレースがログの中に生成され、将来の長く運用しているコードのデバッグに使えます。
:::ruby
# config/initializers/timeout.rb
Rack::Timeout.timeout = 10 # seconds
Ruby 1.9/2.0 では, Rack::Timeout
は Rubyのstdlib Timeout
library を使っていますがこれは信用できるものでない可能性があります。HerokuはRack::TimeoutとUnicornのタイムアウト設定をつかうことをおすすめします。もし両方のタイムアウトの仕組みを使う場合、Rack::Timeoutが生成するスタックトレースをデバッグ用に使おうと考えているならば、Rack::Timeoutの値はUnicornのタイムアウトの値よりも低くあるべきです。
Unicorn をサーバとしてProcfileの中のウェブプロセスに登録します。設定ファイルのなかにポイントします:
web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb
Unicornを使ったサンプルのRails3のアプリがここにあります:
https://github.com/heroku/ruby-rails-unicorn-sample
他の外部サービスとのコネクションがUnicornの分岐モデルに適応して動くかどうかに気をつけてください。上記のサンプル設定ファイルでも見られるように、アプリケーションはbefore_fork
ブロック内でActiveRecordとのコネクションを破棄し、after_fork
のワーカープロセス内で再接続をします。他のサービスも似たような形になるはずです。例えば、これはResqueをUnicornアプリケーションで使った場合の設定ブロックです。
:::ruby
before_fork do |server, worker|
# ...
# If you are using Redis but not Resque, change this
if defined?(Resque)
Resque.redis.quit
Rails.logger.info('Disconnected from Redis')
end
end
after_fork do |server, worker|
# ...
# If you are using Redis but not Resque, change this
if defined?(Resque)
Resque.redis = ENV['REDIS_URI']
Rails.logger.info('Connected to Redis')
end
end
dalli memcache clientのような、多くの一般的なGemは彼らのドキュメントの中でUnicornのワーカープロセスとの親和性について議論をしています。もしあなたが問題を抱えているならば、より多い情報を得るためにも、あなたのGemのドキュメントを確認してください。
最適なパフォーマンスのために、あなたのアセットをCDNにあげ、あなたのWebDyno達を動的なコンテンツの提供にのみ集中させてあげてください。
製品として並列的なウェブサーバを運用することは、それぞれのDynoが一つ以上のデータベースコネクションを必要とすることを意味します。たくさんの容量のRailsアプリケーションを並列的なウェブサーバで走らせる事は、ActiveRecordがコネクションプールの中でこれらのコネクションをどうやって作成し管理しているか、またデータベース開発におけるコネクションの制限について理解する必要があるでしょう。これらのトピックに関する深い話を確認するために、DevCenterの並列性とデータベースコネクションの記事を一読ください。
Unicorn がHerokuで走るRubyアプリケーションのための標準のウェブサーバとして推奨されている間は、あなたの判断でのアプリケーションやウォークロードの組み合わせは、最良の選択とはなり難いでしょう。ですが、特にもしあなたのアプリケーションが遅いクライアントからボディが大きいリクエストを受け付ける場合、あなたは別のウェブサーバと使った方がいいでしょう。一つの例として、wifiや4Gのような早いネットワークを使っていない携帯端末から、ユーザが画像をアプリに対して送信する場合が考えられます。
問題はUnicornのワーカーがクライアントからゆっくり送られてくるリクエストを受けるのに忙しくなってしまうことで起こります。もし全てのUnicornのワーカーが忙しくなってしまった場合、新しいリクエストはキューに積まれ、あなたのアプリは普通じゃないリクエストキューの時間か、H12 errorsを経験するでしょう。
Puma, Thin そして Rainbows! は遅いクライアントが負荷をかける場合によりよく動く代替のウェブサーバです。Herokuのあなたのアプリケーションを走らせるウェブサーバを変更する場合は、単純にProcfileの中のweb
プロセスタイプに別のコマンドを定義してください。