dely Tech Blog

クラシル・TRILLを運営するdely株式会社の開発ブログです

1台あたり10,000人を捌くRails製Webサーバのチューニング

SREの深尾です。kurashiru [クラシル] のインフラを担当しています。

タイトルのとおり、クラシルのwebサイトではRailsを使っており、1サーバあたり10,000人程度のアクセスに耐えることができます。実際には余裕を持たせて5,000人/サーバを目安にスケールさせており、TV CMをガンガンやったり、国内外のTV番組で特集されたり、芸能人にSNSで拡散されても動じませんが、実は過去に1度だけWebサイトがダウンしてしまったことがあります。それは2017年3月11日にSmaSTATION!!というTV番組でクラシルが取り上げられた時のことでした。

以下はその時のリクエスト数を表すグラフです。ダウンしてしまったので計測できなかったユーザの数字は含まれませんがそれでもアクセス数は1分で数万人を超えていました。

f:id:motobrew:20170612121013p:plain

それまで、Webサイトの負荷対策はあまり行っておらず、2台のWebサーバをLoad Balancerに繋いで冗長化しているだけでした。まず負荷対策においてサーバの数を増やすだけではダメなのかということについて考えたとき、以下の理由からサーバを増やすだけではなくボトルネックを調査した方が良いと言えます。

  • ボトルネックがわからないとスケーラビリティを保証できない
  • 仕組みを理解していないと障害原因の特定が難しい
  • 1台あたりの限界がわからないと適切にサイジングできない
  • リソースを使い切れないとインフラ費用が何倍にも膨れ上がる

スマステでサービスがダウンした時、CPU使用率が20%を超えることはなく以下のようなエラーログを出し、nginxが502を返していました。

[error] 2820#0: *10321 connect() to unix:/tmp/web/unicorn.sock failed (111: Connection refused) while connecting to upstream,

そこでCPUの利用効率を上げるために同時に捌くリクエストの数を増やすことを考えました。

 Unicornのプロセス数を増やす

Unicornは1プロセスあたり1リクエストずつ捌く設計になっています。そのため1サーバあたり同時に捌けるリクエストはworker_processesで設定された値になります。

デフォルトでは5になっていました。これを増やせば同時に捌くリクエストを増やすことができます。どれくらいが適切かはアプリケーションによって変わります。例えば、リクエストの裏側でDBや全文検索エンジンなど、別のサーバにリクエストを投げてそのレスポンスを待っている間は、Rails側のサーバのCPUは使われません。その分Rails側のCPUは余裕があることになりますが、プロセス数を増やすとメモリ使用量も増えます。仮にメモリ使用率が100%近くになってしまうと、OSが使うファイルシステムキャッシュのメモリが少なくなってパフォーマンスが悪くなってしまうことがあります。そのため、以下のことを考慮しながら、本番環境でレイテンシなどのパフォーマンスを測りながら細かくチューニングすると最適な値が見つかると思います。

  • プロセス数に比例してメモリ使用量が増える
  • コア数に対して多すぎるとロードアベレージが上がり、オーバーヘッドが大きくなる
  • メモリの未使用領域はファイルシステムキャッシュに利用される

プロセス数を増やすことでCPUを使いきれるかと思ったのですが、abコマンドで簡単な負荷テストを実施したところ、実際には同時接続がおよそ1000を超えるとCPUやメモリはまだ余裕があるのにnginxがエラーを返していました。そこで次のチューニングです。

UNIXソケットのバックログを増やす

瞬間的におよそ1000を超えるリクエストがきた場合はエラーになってしまうことがわかりましたが、1台のサーバで同時に処理できるリクエストはUnicornのプロセス数なので、プロセス数を超えるリクエストは1000ぐらいまでどこかで順番待ちをしていることになります。

弊社ではNginxからUnicornへリバースプロキシにUNIXソケットを利用しており、Unicornでこのソケットのbacklogがデフォルトで1024になっているのではないかという仮説を立てました。つまり、backlogを増やせば一時的に数千以上のリクエストが来ても順番待ちをさせて返すことができるのではないかというわけです。そこで環境変数で以下のように設定します。

unicorn socket backlog

backlogを4096に変更したところ、1500や2000以上のリクエストを同時に投げてもエラーにならずに(少し時間はかかるけれど)正常なHTTPレスポンスを返すことができました。また、負荷テストを行ったところ、CPUを100%近くまで使える状態になりました。

最後に

f:id:motobrew:20170621213240j:plain

このようにボトルネックを1つずつ解消するというプロセスを繰り返すことでハードウェアリソースを使い切ることができます。また上記以外にNginxのキャッシュを利用して、瞬間的なアクセスにはNginxのキャッシュを返すことで1台あたり10,000人程度でも高速に捌けるサーバとなっています。最後にサーバのサイズとプロセス数とバックログの参考値を以下に記載します。

  • サーバ:CPU2コア、メモリ8GB
  • worker_processes:24
  • backlog:8192 

 

delyでは、急速なサービス成長を支えるSREを募集しています。

www.wantedly.com

www.wantedly.com