ユニコーンの紹介


Rails開発者であれば、複数のリクエストを同時に処理できるHTTPサーバーであるUnicornについて聞いたことがあるでしょう。

Unicornは、フォークされたプロセスを使用して同時実行性を実現します。 フォークされたプロセスは本質的に相互のコピーであるため、これはRailsアプリケーションがスレッドセーフである必要がないことを意味します。

私たちの自身のコードがスレッドセーフであることを保証するのは難しいので、これは素晴らしいことです。 コードがスレッドセーフであることを保証できない場合は、 Puma などの同時Webサーバー、およびJRubyRubiniusなどの並行性と並列性を活用する代替Ruby実装もあります。 ]は問題外です。

したがって、Unicornは、スレッドセーフでない場合でもRailsアプリに同時実行性を提供します。 ただし、これにはコストがかかります。 Unicornで実行されているRailsアプリは、はるかに多くのメモリを消費する傾向があります。 アプリのメモリ消費量に注意を払わなくても、クラウドサーバーに過負荷がかかっていることに気付くかもしれません。

この記事では、メモリ消費を制御しながら、Unicornの同時実行性を活用するいくつかの方法を探ります。

Ruby 2.0を使用してください!


Ruby 1.9を使用している場合は、Ruby2.0への切り替えを真剣に検討する必要があります。 その理由を理解するには、フォークについて少し理解する必要があります。

フォークとコピーオンライト(CoW)


子プロセスがフォークされると、それは親プロセスとまったく同じコピーになります。 ただし、コピーされた実際の物理メモリを作成する必要はありません。 これらは正確なコピーであるため、両方の子プロセスと親プロセスが同じ物理メモリを共有できます。 write が作成された場合にのみ、子プロセスを物理メモリにコピーします。

では、これはRuby 1.9 / 2.0およびUnicornとどのように関連していますか?

ユニコーンがフォークを使用していることを思い出してください。 理論的には、オペレーティングシステムはCoWを利用できます。 残念ながら、Ruby1.9ではこれが可能ではありません。 より正確には、Ruby1.9のガベージコレクションの実装ではこれは不可能です。 非常に単純化されたバージョンはこれです— Ruby 1.9のガベージコレクターが起動すると、書き込みが行われるため、CoWは役に立たなくなります。

詳細に立ち入ることなく、Ruby 2.0のガベージコレクターがこれを修正し、CoWを悪用できるようになったと言えば十分です。

Unicornの構成の調整


調整できる設定がいくつかあります config/unicorn.rb ユニコーンからできるだけ多くのパフォーマンスを引き出すために。

worker_processes

これにより、起動するワーカープロセスの数が設定されます。 oneプロセスに必要なメモリ量を知ることが重要です。 これは、VPSのRAMを使い果たしないように、ワーカーの数を安全に予算化できるようにするためです。

timeout

これは小さい数値に設定する必要があります。通常、15〜30秒が妥当な数値です。 この設定は、ワーカーがタイムアウトするまでの時間を設定します。 比較的低い数値を設定する理由は、長時間実行されるリクエストが他のリクエストの処理を妨げないようにするためです。

preload_app

これはに設定する必要があります true. これをに設定する true Unicornワーカープロセスを起動するための起動時間を短縮します。 これは、CoWを使用して、他のワーカープロセスをフォークする前にアプリケーションをプリロードします。 ただし、bigの落とし穴があります。 ソケット(データベース接続など)が適切に閉じられ、再度開かれるように特に注意する必要があります。 これを使用して before_forkafter_fork.

次に例を示します。

before_fork do |server, worker|
  # Disconnect since the database connection will not carry over
  if defined? ActiveRecord::Base
    ActiveRecord::Base.connection.disconnect!
  end
  
  if defined?(Resque)
    Resque.redis.quit
    Rails.logger.info('Disconnected from Redis')
  end
end

after_fork do |server, worker|
  # Start up the database connection again in the worker
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.establish_connection
  end
  
  if defined?(Resque)
    Resque.redis = ENV['REDIS_URI']
    Rails.logger.info('Connected to Redis')
  end
end

この例では、ワーカーがフォークされたときに接続が閉じられ、再び開かれることを確認します。 データベース接続に加えて、ソケットを必要とする他の接続も同様に扱われるようにする必要があります。 上記には、Resqueの構成が含まれています。

あなたのユニコーン労働者の記憶消費を飼いならす


明らかに、それはすべての虹とユニコーンではありません(しゃれが意図されています!)。 Railsアプリがメモリをリークしている場合-Unicornはそれを悪化させます。

これらのフォークされたプロセスはそれぞれ、Railsアプリケーションのコピーであるため、メモリを消費します。 したがって、ワーカーが増えるということは、アプリケーションがより多くの着信要求を処理できることを意味しますが、システムにある物理RAMの量に制限されます。

Railsアプリケーションがメモリをリークするのは簡単です。 すべてのメモリリークをなんとか埋めることができたとしても、対処すべき理想的なガベージコレクターはまだありません(私はMRIの実装について言及しています)。

上記は、メモリリークのあるUnicornを実行しているRailsアプリケーションを示しています。

時間の経過とともに、メモリ消費量は増加し続けます。 複数のUnicornワーカーを使用すると、メモリが消費される速度が加速され、RAMがなくなるまでになります。 その後、アプリケーションは停止し、不幸なユーザーや顧客の大群につながります。

これはではなくユニコーンのせいであることに注意することが重要です。 ただし、これは遅かれ早かれ直面する問題です。

ユニコーンワーカーキラーに入る


私が遭遇した最も簡単な解決策の1つは、unicorn-worker-killergemです。

README から:

unicorn-worker-killer gemは、に基づいてUnicornワーカーの自動再起動を提供します

  1. リクエストの最大数、および
  2. 要求に影響を与えることなく、メモリサイズ(RSS)を処理します。

これにより、アプリケーションノードでの予期しないメモリの枯渇を回避できるため、サイトの安定性が大幅に向上します。

すでにUnicornをセットアップして実行していると想定していることに注意してください。

ステップ1:

追加 unicorn-worker-killer あなたのGemfileに。 このの下に置きます unicorn 宝石。

group :production do 
  gem 'unicorn'
  gem 'unicorn-worker-killer'
end

ステップ2:

走る bundle install.

ステップ3:

ここに楽しい部分があります。 を見つけて開きます config.ru ファイル。

# --- Start of unicorn worker killer code ---

if ENV['RAILS_ENV'] == 'production' 
  require 'unicorn/worker_killer'

  max_request_min =  500
  max_request_max =  600

  # Max requests per worker
  use Unicorn::WorkerKiller::MaxRequests, max_request_min, max_request_max

  oom_min = (240) * (1024**2)
  oom_max = (260) * (1024**2)

  # Max memory size (RSS) per worker
  use Unicorn::WorkerKiller::Oom, oom_min, oom_max
end

# --- End of unicorn worker killer code ---

require ::File.expand_path('../config/environment',  __FILE__)
run YourApp::Application

まず、私たちはにいることを確認します production 環境。 その場合は、先に進んで次のコードを実行します。

unicorn-worker-killer 最大リクエストと最大メモリの2つの条件でワーカーを強制終了します。

最大リクエスト数

この例では、ワーカーが between 500〜600のリクエストを処理した場合、ワーカーは強制終了されます。 これは範囲であることに注意してください。 これにより、複数のワーカーが同時に終了する可能性が最小限に抑えられます。

最大メモリ

ここで、ワーカーが between 240〜260 MBのメモリを消費すると、ワーカーは強制終了されます。 これは、上記と同じ理由で範囲です。

すべてのアプリには固有のメモリ要件があります。 通常の操作中のアプリケーションのメモリ消費量を大まかに把握する必要があります。 そうすれば、ワーカーの最小メモリ消費量と最大メモリ消費量をより正確に見積もることができます。

すべてを適切に構成したら、アプリをデプロイすると、メモリの動作の不安定さが大幅に軽減されます。

グラフのねじれに注意してください。 それがその仕事をしている宝石です!

結論

Unicornは、スレッドセーフであるかどうかに関係なく、Railsアプリケーションに並行性を実現するための簡単な方法を提供します。 ただし、RAM消費量が増えるというコストが伴います。 RAM消費のバランスを取ることは、アプリケーションの安定性とパフォーマンスにとって絶対に不可欠です。

ユニコーンワーカーを最大のパフォーマンスに調整する3つの方法を見てきました。

  1. Ruby 2.0を使用すると、コピーオンライトのセマンティクスを活用できるように、ガベージコレクターが大幅に改善されます。

  2. のさまざまな構成オプションの調整 config/unicorn.rb.

  3. 使用する unicorn-worker-killer 肥大化しすぎたときに労働者を殺して再起動することにより、優雅に問題を解決する。

資力


  • Ruby2.0ガベージコレクターとコピーオンライトセマンティクスがどのように機能するかについてのすばらしい説明

  • ユニコーン構成オプションの全リスト