Skip to content
shunter1112 edited this page Aug 1, 2013 · 1 revision

プロセスが並列にリクエストを投げるようなウェブアプリケーションは、同じ時間に一つだけしかリクエストを投げないアプリケーションに比べて、Dynoのリソースをより効率的に使える状態になります。従って、並列リクエスト処理は本格的なサービスを開発、運用するいかなる場合でも推奨されます。

Railsは元々は同時に1リクエストしか処理しないように設計をされています。そして、徐々にこの設計から、単体のRubyプロセス内でリクエストの並列処理を許すようなスレッドセーフの実装へと移行しています。しかし今日では、ほとんどのRubyアプリケーションはサポートをしていません。

Unicornウェブサーバは、一つのDynoで複数のRubyプロセスを走らせる事で、どんなRailsアプリケーションでも並列に走らせられるようになります。

このガイドでは、Unicornウェブサーバを使ったHerokuへのRailsアプリケーションのデプロイを見て行きます。基本的なRailsのセットアップはGetting Started with Railsをご覧ください。

製品としてあなたのアプリをデプロイする前に、いつもステージング環境で新しくデプロイされた物をテストしてください。

Unicorn サーバ

Unicornは入力されるリクエストを同時に制御するために分岐されたプロセスを使うRack HTTP サーバです。

アプリケーションへのUnicornの追加

Gemfile

始めに、ユニコーンをあなたのアプリの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のドキュメントをご覧ください。

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エラー(メモリ割当超過)をあなたのアプリのログから監視してください。

アプリの事前読込(Preload)

:::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_forkafter_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には調整可能なタイムアウト設定があります。タイムアウトは、Unicornがあなたのアプリによってリクエストが処理され始めたときにカウントダウンが始まり、レスポンスが帰るときに終わります。もしリクエストが決まった時間よりも長い時間を取っている場合、マスターはリクエストを持つワーカーにSIGKILLします。

:::ruby
timeout 15

私たちは15秒のタイム会うとを推奨します。15秒のタイムアウトを設定していると、マスタープロセスはリクエストの処理に15秒以上時間がかかっているワーカに対してSIGKILLを送ります。この場合はH13エラーを生成し、ログの中で確認することができます。注意すべき点として、これはデバッグを支援するためのスタックトレースは生成しません。

Rack::Timeout

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のタイムアウトの値よりも低くあるべきです。

Procfile

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プロセスタイプに別のコマンドを定義してください。

Clone this wiki locally