EC2とS3をうまく使い分けてワールドビジネスサテライト砲を確実に乗り切る方法

mat2uken mat2uken

まつけんです。

今回は、とれたまのコーナーで有名なテレビ東京さんの番組「ワールドビジネスサテライト」(以下、WBS)で紹介されるにあたって、どういう負荷対策をしたのかを紹介します。
WBSに紹介されて直後にサーバーアクセス殺到して落ちました、というような話は結構聞いていたので、機会損失という意味でも落ちるのはなんとか避けたいね、ということで対策をしましょうという話になりました。

前提

今回の負荷対策の前提となる条件を挙げます。

まず、ありがたいことにCerevoは最近、WBSに2度紹介されています。
LiveShellの紹介(映像はこちら)とCerevo DASHの紹介(映像はこちら)の2回です。
Cerevo DASHでは現在、iConvexというiPhoneアプリと連動する電子巻き尺ガジェットの支援を募集しています。是非、ご覧ください。
ということで、それぞれでの紹介内容にあわせて、別のサイト+コーポレートサイトがあるので、アクセスが増える可能性があるサイトは計3つあります。

LiveShell – http://shell.cerevo.com
Cerevo DASH – http://dash.cerevo.com
コーポレートサイト – http://www.cerevo.com
になります。

また、弊社は基本的に現時点では、ほぼすべてのサイトをAWS上で運営しているので、インスタンスの追加や変更は比較的に容易に行うことができます。
最後に、WBSに限らずなのですが、取材がくるよと決まってから実際に放映されるまでは大抵の場合、あまり時間がないです。
今回の場合も、1週間もないくらいの期間でできる対策をしないといけないという縛りもありました。

静的なサイト

まずは、負荷対策というか、もともとある程度であれば負荷が急に増えたとしても問題ないサイトがCerevo Dashとコーポレートサイトです。
これは単純にS3 websiteをつかって、静的なファイルしか置いてません。
そのため、S3が勝手に頑張ってくれるので、少々アクセスが増えたところで、なんなく乗り切ってくれました。
ログを見る限り、ピークでは500req/sくらいのアクセスがあったようですが、なんの問題もありませんでした。
TokyoにBucketがあって、テレビ番組の負荷であれば、アクセスの大半が国内からであることもわかっている場合、S3に置いておくだけで、充分っぽいねということが分かりました。

LiveShellサイト

そして、本題のLiveShell向けのサイトです。
こちらは、R53,ELB,EC2,RDSを使った、割とスタンダードな構成(だと思っています)のサイトです。

まずは、負荷対策をどこにどういう風に施していくかという部分です。
LiveShell向けサイトはその機能を考えると、大きくわけて2つの種別のページに分けることができます。
1つ目は、上記のLiveShellを操作するためのDashboardに関連する、ログイン後に操作されるLiveShell関係のページ。
2つ目は、それ以外の製品紹介、事例紹介、スペック、マニュアル、FAQなどのLiveShellの購入前に主に閲覧されるページ。

そして、テレビで紹介された場合に、アクセスが増えると予想されるのは、2つめの購入前に閲覧されるページです。
逆に、1つ目のページのアクセスが増える(LiveShellの配信が一時的に急増する)上がるという場合は、正直、スケールアップぐらいしか短期間ではできそうにありません。ここの負荷があがった場合はどこまででもスケールアップで後追いで対応しようということで、c1.mediumまでスケールアップしておき、当日は張り付いてモニタリングだけすることにしました。
#基本的に、メモリは常に余っている状態で、CPU負荷の方が高いため、HighCPUなインスタンスを選択しています。

では、2つ目のページ群の負荷対策をどうするかということになります。
まず、LiveShell向けのページは全般的にすべて多言語対応されており、日本語、英語の両方の表示に対応しています。
その言語切替などのためもあり、製品紹介などのページなども、他のページと同様に、基本的にテンプレートを読み込んで動的に毎回生成している状況でした。
しかし、アクセスが急増した場合、トップページ、製品紹介、特徴、スペックといったページに集中した場合、そこがボトルネックになる可能性が高いのは明らかです。
そのため、memcachedでキャッシュするとか、いくつか案を考えましたが、そもそもキャッシュをしたところで、動的なコンテンツになっている時点で、様々な箇所に負荷がかかる可能性があり、チューニングをするにしても慎重に行う必要がありそうで、間に合う気がしませんでした。
というわけで、もうすこしシンプルな手として、そういったページは、開発時は動的に生成、実際に本番環境に適用されるタイミングで、静的ページに書き出してデプロイという風に運用することに変更しました。

この対策をすると、アクセスが急増したときに、nginxがどれくらいの負荷を捌けるかという問題のみに集中することができます。
というわけで、次はnginxからの静的ページ配信性能をどうするかを考えます。
静的なファイルとはいえ、アクセス時にディスクから読み込まれるような事態になれば明らかに間に合いません。
そうすると、メモリに乗っかっていることが前提になるので、メモリの使用状況を考えます。
このインスタンスでは、動的部分を担当するアプリも動いているので、そちらが使っているメモリを差し引きます。これは動作実績を見ると、多いときでも600MB程度です。
もともとm1.smallでも1.7GBはメモリがあり、その他のプロセスやカーネルの利用量を見ても、1GB程度のメモリは常に空いている状態です。上記で生成された静的コンテンツを集計しても、100MBにも届きません。つまり、基本的にはnginxがディスクから配信する際はOS側のページキャッシュに任せるだけで、メモリに載った状態でディスクアクセスなしにファイルを配信してくれるはずです。

ここまで検討したら、あとはnginxで配信できる性能をどこまで高めるかという問題です。
まず、m1.smallは1ECUということで、ここでも言及されているとおり、CPUを常につかえるわけではないインスタンスです。さすがに負荷がかかるときに、このインスタンスを増やしていくというのはナンセンスなので、メモリも足りていることをかんがえると、HighCPUなインスタンスを複数台用意して配信するのがいいだろうということになりました。
というわけで、abなどをつかって配信性能を測定して、最低でも2000req/sくらいのアクセスは耐えられるようにということで見積をして、c1.medium2台で充分だなとおもった後に、心の平穏のために盛って、c1.mediumを4台用意しました。

実際の負荷

WBSで紹介された場合、そのアクセスの増加ペースは想像以上に瞬間的です。
大体紹介がはじまって、1-2分の間に、いつもは20req/sくらいのアクセスが、800req/sくらいにぐぐっと伸びました。その後、コーナー終盤までくると、1000req/sくらいまでピークのアクセスがきます。
その後は、スーッとアクセスは減っていき、それでも2時間くらいは300req/sくらいのアクセスが観測されます。その後は順調にアクセスは減り、朝3-4時には50req/sくらいまで落ちました。その後は1日くらいかけて緩やかにアクセスは減っていき、4日目くらいには平常な状態に戻りました。
WBSのおもしろいところは、放送翌日の8-10時です、あきらかにアクセスがまた増えます。一瞬ですが、300req/sくらいまで上がりました。これはおそらく、自宅でWBSを見て、出勤後にチェックという人がかなりいるからなのだろうな、と。

時系列では
〜23:00 20req/s
23:15(紹介始まる) 800req/s
23:20(紹介終わり) 1000req/s
23:30 – 01:30 300req/s
02:00 – 08:00 50req/s
08:00 – 10:00 300req/s
10:00〜3日後くらい 50req/s
それ以降は平常に戻る
という感じの流れでした。

負荷としては、大体、CPU負荷で40-50%くらいがピーク時に出ただけで、応答速度も遅いときも600msくらいで、平均は200-300msくらいに落ち着いていました。
c1.medium 4台用意しておけば、このくらいのアクセスであれば、まったく問題なくむしろオーバースペックだった感が漂いましたが、まあ落ちてうわーっとなるよりは全然良いし、数日、c1.mediumを立ち上げておいてもそれほど大きなコストではないのでよしとしました。

まとめ

というわけで、負荷対策として、以下を行いました。
・アクセスが増えると考えられるページに関しては、デプロイ時に静的なコンテンツを生成して、配信をnginxに任せる
・nginxがページキャッシュを利用して、ディスクI/Oを発生させずに配信できるようにメモリ消費を調整する、もしくは、メモリが充分に確保できるインスタンスを使う
・nginxの配信性能をベンチマークして予測されるアクセスに耐えられる程度にインスタンスを増やす
という形で充分乗り切れました。
また、動的な部分に関しても、ログイン画面や新規登録画面へのアクセスは若干増えましたが、基本的には予想通り、アクセスが大幅に増えることもなく、c1.mediumで充分に乗り越えることができました。

おまけ

LiveShellサイトの構成のご紹介。割とスタンダードなAWSを使った構成(だと思っています)をしています。

ELB—EC2(nginx+gunicorn)—RDS(MySQL)+EC2+EBS(KyotoTycoon)

アプリは、基本的にPythonで書かれており、WEBフレームワークはWerkzeugというか、Flaskをベースとしたmyojinという独自フレームワークをつかっています。(これは実はBitBucketで公開しています。ドキュメントもなにもないですが。)

EC2上で、Ubuntu 11.10が動作しており、そこにnginxをリバースプロキシとしておいて、同一インスタンス上でgunicorn経由でmyojinが起動されています。
そのアプリが動いているEC2のインスタンスは、m1.smallが2台。
KyotoTycoonは、LiveShellの状態など一時保存のためのKVSとしてm1.small上で動作しています。
RDSもMySQLでMultiAZは有効になっているけれど、m1.smallインスタンスで動かしていて、特にリードレプリカなども用意していません。
(そもそも、一般的なサイトにくらべて、LiveShellを操作するという機能は情報を永続化する必要がないので、SQLを発行する回数はかなり少ないという側面がありますが。。。)
というくらいの上記の通りで、規模としては大変小さいというか、コストはあまりかけずに運営しています。

これ以外に、LiveShellをリモートで配信状況をモニタしたり、逆に音量、画質など様々な設定をリアルタイムに操作する機能があり、そのサーバー側の機能を総称してDashboardと呼んでいます。こちらのツール的な部分の機能は、websocket(というか、Socket.IO)をつかってページを開いている間はTCPが常時接続されていたり、LiveShellとはUDPでやりとりをしてNAT越えを実現していたりという感じで、そのために、node.jsやPythonでかかれたデーモンが動いています。
こちらも平常時は、m1.smallで動いており、わりと低い負荷で収まっています。

おまけ2

ちなみに、サイトの高速化の一環として、Javascriptは基本的にGoogle Closure Libraryで書かれていてADVANCED MODEでコンパイルされ、1つのファイルにまとまっています。
CSSに関しても、基本的に1つのファイルにまとめるようになっています。
そのうえで、そういったファイルは、リビジョン番号ごとにディレクトリを変化させ、一度読み込むとキャッシュさせるために、長大(1年)なExpiresを設定しています。
また、事前に静的ファイルはgzipに圧縮されており、nginxのgzip_staticによって配信されるようになっています。
画像ファイルに関しては、おなじようなことができていないですが、こちらも続いてやりたいなぁとおもっています。

このあたりによって、上のリクエスト数の数字は他のサイトに比べると、閲覧数の割に少なくなっている傾向にある可能性はあります。
そういう意味でも、この手の施策は、アクセス数の削減につながるので、負荷対策としてもそれなりの効果があるのではないかと思っています。