Apache×Railsのチューニングで@cosmeの最大イベントを乗り切った

この投稿はアイスタイル Advent Calender 2020 の18日目の記事です。

はじめに

皆さんこんにちは!
早くもアイスタイル3年目突入のnakazawayです!

アイスタイルでは毎年、@cosme Beauty Day という一大イベントを開催しております!
弊社が運営するECサイト @cosme SHOPPING や 弊社が運営する実店舗 @cosme STORE / @cosme TOKYO にてお得なセールを実施する大規模イベントです!

今回はそんな一大イベントの際に当然対応しなくてはならない、負荷対策についてお話しさせていただきます!

@cosme Beauty Day について①
@cosme Beauty Day について②

@cosme Beauty Day 2020

前提

担当システム

自分が担当しているシステムは「会員基盤」といって、弊社サービス全体で共通利用される、ログイン/会員登録/会員情報の操作等を担うシステムです。
よって、@cosme Beauty Day でも裏でガンガン活躍するので、負荷対策は万全を期す必要があるんです。。。

今までの問題

従来会員基盤では一定のリクエスト負荷を受けると耐えられなくなっており、サーバ増設などの手段で乗り切っていた過去があります。
原因としては、ApacheのMaxClientsの上限に達してしまっているというものでしたが、どれだけApacheのMaxClientsをあげても、目まぐるしく成果が出るわけではなかったため、その他の設定値も見直す必要がありました。
今回はその設定値を変えてチューニングをする話となります。
※負荷に耐えることができる限界値や、負荷対策によって目標にする値に関しては、社外秘のため本記事では控えさせていただきます。

目次

  1. どこをどう直していくか
  2. 数値の計算
  3. Passengerのチューニング
  4. Apacheのチューニング
  5. 結果

1. どこをどう直していくか

まず、負荷対策対象の詳細を記します。

アプリケーションの構成

今回対策するアプリケーションは以下の構成になっています。

項目 名称
言語 Ruby
フレームワーク Ruby on Rails
webサーバ Apache
OS CentOS

webサーバのアーキテクチャ

webサーバは上述の通りApacheで、フレームワークはRuby on Rails です。ApacheでRailsを動かすのにPassengerを利用しています。
アーキテクチャの図は以下となります。

webサーバのアーキテクチャ図

今までの問題の解決策

会員基盤では、webサーバのアーキテクチャを見ていただいた通りApache×Railsですので、リクエストのプロセスの捌き方がブロッキングI/Oとなっております。
よって、カーネルが処理を捌き切る前に後続のリクエストがあると、その分リクエストが滞留する仕組みになっています。
まずはRails側のPassengerにプロセスが滞留して、その後Apache側にもプロセスが滞留していきます。
滞留可能なリクエスト(プロセス)の上限数は、設定によって決めれらており、PassengerはPassengerMaxPoolSize、ApacheはMaxClientsの値となります。
よって、この設定値を適切な値に設定することで、負荷対策が可能となるわけです。

設定値を上げすぎてもだめ

もちろん、ただ単に設定値を上げればいいというものではありません。
アプリケーションが乗っているマシンのメモリ許容量を超えたリクエストがあればOOMなどの問題に繋がり、OS自体がダウンしてしまう恐れがありますので、設定値は計算した上で適切に設定する必要があります。

2. 数値の計算

PassengerのPassengerMaxPoolSizeの計算

PassengerMaxPoolSizeの適正値を計算するには単純に、マシン上でpassengerが利用できるメモリ量を決めて、それをPassengerの1プロセスのメモリ消費量で割ります。
記事上では仮でマシンのメモリを8GiB(8192MiB)、Passengerが使用できるマシン上のメモリ消費率75%、Passengerの1プロセスのメモリ消費量を100MiBとします。

計算式は以下です。

PassengerMaxPoolSize = (マシンのメモリ総量 * Passengerが利用できるメモリ消費率) / Passengerの1プロセスのメモリ消費量
PassengerMaxPoolSize = (8192 * 0.75) / 100

この場合だと約60がPassengerMaxPoolSizeと判断できます。

ApacheのMaxClientsの計算

Passengerのプロセスですでに75%のマシンメモリを消費する計算ですので、Passengerで溢れた分のプロセスをApacheで滞留させるべきではないです。
よって、PassengerMaxPoolSizeに設定する以上の数値を設定してもそれ以上パフォーマンスが上がるわけでもなければ、逆に滞留させすぎてOOMなどに繋がります。
そのことから、MaxClientsはPassengerMaxPoolSizeと同値にしました。

PassengerMaxPoolSizeもMaxClientsも同じ数値でメモリは溢れない?大丈夫?

同値にしたらApacheの方でもメモリ消費量が増えてメモリが溢れてしまうのではないか?と思う方もいるかもしれません。
しかし、Apacheのメモリ消費量はPassengerに対してだと(作りにもよりますが)微々たるものです。
一般的には10MiBくらいだと言われています。会員基盤でも調べたところ1プロセスあたり0.4MiBでした。
最終的に、Passengerのメモリ消費量とApacheのメモリ消費量の合算値がマシンのメモリ内におさまっていれば問題ありません。
仮にApacheのメモリ消費量を10MiBと見積もっていたとしたら、消費量は以下となります。

Apacheのトータルメモリ消費量= 10 * 60

600MiBとなり、Passengerのメモリ消費量を足しても6744MiBと、マシンのメモリ消費量の約82%程しか使用しないことになり、ある程度余裕を持たせた状態となります。

【おまけ】各プロセスのメモリ消費量の出し方

今回は仮でPassenger:100MiB、Apache:10MiBとしましたが、現状実際にどの程度のメモリを消費しているのかを確認するには以下コマンドがおすすめです。

# passenger-memory-stats
-------------- Apache processes ---------------
PID    PPID   Threads  VMSize    Private  Name
-----------------------------------------------
1111   1001   1        200 MB  10 MB   /hoge/fuga/httpd
1111   1001   1        200 MB  10 MB   /hoge/fuga/httpd
1111   1001   1        200 MB  10 MB   /hoge/fuga/httpd
1111   1001   1        200 MB  10 MB   /hoge/fuga/httpd
1001   1      1        200 MB  10 MB   /usr/sbin/httpd
### Processes: 5
### Total private dirty RSS: 50 MB

--------- Passenger processes ----------
PID    Threads  VMSize    Private  Name
----------------------------------------
1111   1        150 MB  100 MB  Rails: /hoge/fuga/piyo/hogehoge/20201218
2222   1        150 MB  100 MB  Rails: /hoge/fuga/piyo/hogehoge/20201219
3333   1        150 MB  100 MB  Rails: /hoge/fuga/piyo/hogehoge/20201220
4444   1        150 MB  100 MB  Rails: /hoge/fuga/piyo/hogehoge/20201221
5555   9        100 MB   5   MB   /hoge/fuga/piyo/passenger
### Processes: 5
### Total private dirty RSS: 405 MB

PassengerやApacheそれぞれの「1プロセスあたりのメモリ消費量」と「トータルのメモリ消費量」がわかります。
「1プロセスあたりのメモリ消費量」は”Private”フィールド、
「トータルのメモリ消費量」は”private dirty RSS”フィールドの値です。

3. Passengerのチューニング

PassengerMaxPoolSizeの設定

実際にPassengerMaxPoolSizeを設定していきます。
設定はApacheのConfigファイル(デフォルトだと /etc/httpd/conf/httpd.conf)で行います。

$ cd /etc/httpd/conf/
$ vim httpd.conf
// httpd.conf
PassengerMaxPoolSize 6 //デフォルトでは6になっているのでここを60に変更

あとは、Apacheをrestartさせて設定変更を反映して完了です。
(CentOS6以下のため、systemctlではなくserviceコマンドです)

$ service httpd configtest
Syntax OK

$ service httpd restart

これで完了です!

確認

PassengerMaxPoolSizeに関しては、Passengerの状態確認ができるコマンドがあるので、そちらで確認が可能です。

$ passenger-status
----------- General information -----------
max      = 60 // PassengerMaxPoolSize
count    = 4 // 現在確保されているプロセス数
active   = 2
inactive = 2

----------- Applications -----------
/hoge/fuga/piyo/hogehoge/20201218
  PID: 11111     Sessions: 0
  PID: 22222     Sessions: 0
  PID: 33333     Sessions: 1
  PID: 44444     Sessions: 1

4. Apacheのチューニング

MaxClientsの設定

次はMaxClientsの設定をしていきます。
設定はPassengerMaxPoolSizeと同様にApacheのConfigファイルで行います。

$ cd /etc/httpd/conf/
$ vim httpd.conf
// httpd.conf
ServerLimit 60 // 設定できるMaxClientsの最大値となる数値のため、MaxClients以上にしなくてはならない
MaxClients 60

あとは、Apacheをrestartさせて設定変更を反映して完了です。

$ service httpd configtest
Syntax OK

$ service httpd restart

これで全手順完了です!!!

5. 結果

イベントを乗り切ることができた

今回の対応もあって、無事にイベントを乗り切ることができました!
細かいことはお伝えできませんが、数値としては昨年の2倍以上のリクエストに耐えられるようになりました!!

おわりに

これからの対応

今回の対応のように、設定値を変えるだけで対応できるような小手先の対応はもうウチの会員基盤ではできないと思っています。
これからは、他のあらゆる問題に対して根本的な改善を行わなくてはなりません。(言語/フレームワークのバージョンアップ、DBスキーマの見直し、アプリケーションの設計見直し etc…)
そんな課題を一緒に改善したいと思ってくれる方がいらしたら、是非アイスタイルへ!

istyleのサービスの会員基盤を担当しているサーバーサイドエンジニアです。フロントエンドは趣味でやってます。 カメラと映画と緑が好き!!