ISUCON4本選の振り返り
こんにちは。ISUCON4 出題担当スタッフの mirakui です。
あの盛り上がった本選から約一ヶ月が経過してしまいましたが、本選について振り返ってみます。
ISUCON4 の予選は、参加チーム180組以上という過去最大の規模でしたが、本選に出場できたのはその中のたった30組でした。この倍率の高さからも激戦であったことは想像に難くないと思いますが、一体どのような問題で、どのような戦いだったのでしょうか。
テーマは「動画広告配信」
本選問題のテーマは、「動画広告配信」でした。広告リクエストに応じて表示すべき動画クリエイティブを抽選し、5MB 程度の mp4 ファイルを出力するという問題です。
この問題には以下の内容が含まれていました。
- 広告主が動画広告を入稿する API。おもに以下の情報を POST する
- 広告動画ファイル
- ユーザが広告(リダイレクタ)をクリックした時のリダイレクト先 URL
- 広告枠の ID
- 広告表示 API。広告の抽選を行う
- ユーザが広告をクリックしたときのリダイレクタ API
- インプレッション数のカウンタをインクリメントする API。インプレッションが発生したときに XHR で呼ばれる想定
- 動画ファイルを返す API
- 広告主向けにクリック数、インプレッション数を集計してレポートする API。レポートは速報レポートと最終レポートの2種類があり、それぞれ集計の内容が異なる
アプリケーションの概要は上記の通りで、Ruby 参考実装では約230行程度の規模でした。
また、参加者が動作確認をするための簡単な HTML を用意はしていましたが、ベンチマーカがアクセスするためのフロントエンドは無く、JSON API のみから構成されたアプリケーションでした。
本選レギュレーションと当日マニュアル
本選では、以下のドキュメントを競技者に配布しました。これらに目を通していただくと、当日の雰囲気が伝わるかもしれません。
競技環境
本選の競技に用いるサーバ環境は、テコラスさんが提供して下さいました。
今回の競技用サーバは1チームに3台(VM)ずつ提供され、全ての VM には同じようにアプリケーションがセットアップされていました。この3台をどのように使い分けてもいいし、1台だけでもいいというレギュレーションでした。ただ、参考実装では複数のホストで動作するようには作られていないので、3台で負荷分散するためには何らかの工夫をする必要がありました。
負荷走行は全チームで共有のベンチマークサーバから行ないました。ベンチマーカには、負荷走行を行う対象 VM の IP アドレスを1つ以上指定するという方式になっており、3つの IP アドレスを指定した場合は3台にラウンドロビンで負荷がかけられるという仕様になっていました。
競技環境の VM について、1台あたりのおおまかなスペックは以下のとおりでした。
- OS: CentOS 6.5
- CPU: 仮想 2 core (ただし1台のみ 1 core)
- メモリ: 1GB
- HDD: 80GB
本選では3台の VM を「1号機」「2号機」「3号機」と呼んで提供していましたが、このうち「1号機」だけは CPU が 1 core しかなく、他の2台は 2 core あるというちょっとした「ひっかけ」を用意していました。物理ホストの関係でこの方が都合がよかったためです。
今回の競技環境の特徴は、動画広告配信というテーマに比べて非常に貧弱な環境であったことが特徴といえます。特に、メモリが 1GB しかないため、参考実装を動かすのもギリギリという状況でした。
ちなみに、競技中には公開していませんでしたが、物理ホストは各チームで1台ずつを専有するようにしてありました。すなわち、1チームに提供された3つの VM は必ず1つの物理ホスト上に立っていました。
また、こちらも競技中には公開しなかった情報ですが、ベンチマークサーバと各チームの物理ホストとの間のネットワーク帯域は 1Gbps を確保していました。
ISUCON の出題傾向と歴史
ISUCON は競技者同士の戦いであるのと同時に、出題者と競技者の戦いでもあります。
ISUCON の歴史を遡ると、ISUCON1,2 ではキャッシュ戦略の台頭がありました。これは、ビューのレンダリングを巧みにキャッシュすることでスループットを飛躍的に向上させるというものです。実際のウェブアプリケーションのパフォーマンス改善でもよくある戦略と言えます。このような戦略の対策として、出題側はキャッシュしにくいアプリケーションにする工夫を凝らし始めました。
ISUCON3 あたりから、オンメモリ戦略をとるチームが現れ始めました。Go などの高速な処理系を使って、RDB を使わずに全て1プロセス内のオンメモリで返してしまうことで高速化するという技法です。多くの場合、参考実装を捨ててアプリケーションをすべて書き直す必要があるので、競技時間中にこれを実現するのは簡単ではありませんが、うまくいけばキャッシュの難しいアプリケーションでも高得点を期待できます。
オンメモリ戦略への対策
今回の本選では、このオンメモリ戦略への対策を積極的に講じました。その対策は主に以下のとおりでした。
- 1台あたりのメモリが少なすぎる(1GB)
- そもそも最初から Redis を用いたオンメモリな参考実装にしてある
いままでの ISUCON ではアプリケーションのバックエンドとして MySQL を使ってきましたが、今回は初めて MySQL を使わずに、 Redis のみを使っています。
参考実装では、なんと、広告主がアップロードした動画ファイルはすべて文字列として Redis に格納するというむちゃくちゃな設計になっています(実際の業務でこんな設計をしたらお説教ものなので気をつけましょう!)
そんな実装のせいで、初期状態では負荷走行を1度走らせるとスワップアウトが激しく発生するという状況に仕上げてありました。
そんなわけで、多くのチームが Redis に入ってくる動画を適宜ディスクに書き出すように書き換えるところからチューニングを始めたと思われます。ディスクにあるものをメモリに載せるのはよくあるチューニングなので、あえてそれと逆のことをさせるというのが今回の出題意図の一つでした。
ベンチマーカと Cache-Control ヘッダ
本選の競技が始まってしばらくすると上位チームのスコアが軒並み8,000点〜9,000点あたりで停滞しはじめ、終盤まで先頭集団のスコアが団子状態になるという事態が起こりました。
その原因は、ある程度の動画配信スループットになるとベンチマークサーバのネットワーク帯域が頭打ちになってしまうためでした。そのような理由で、9,000点を超えたあたりからスコアがほとんど伸びませんでした。
実はこの状況には打開策が用意してありました。それはベンチマーカが Conditional GET に対応していたという点です。大きくネットワーク帯域を消費する動画配信のレスポンスに、Cache-Control ヘッダを適切に指定することで、ベンチマーカは内部的に動画をキャッシュするようになります。出題側の意図としては、この挙動は、実際の動画ファイル配信では通常 CDN が用いられるということを模倣したものでした。ベンチマーカのこの挙動に気づくことができれば、飛躍的にスループットが上がり、スコアを10万点以上に引き上げることができました。
我々出題スタッフは、多くのチームが上記の Cache-Control を設定するだろうと予想し、高スループットの戦いが始まることを期待して、ベンチマーカやレギュレーションを調整していました。しかし、出題側のヒントの与え方が適切ではなかったこともあり、残念ながらこの仕様に気づくことができたチームは1位の「生ハム原木」と2位の「チームフリー素材」のみでした。結果として共有ベンチマークサーバの帯域が詰まりがちになり、参加者の皆様にストレスをかけてしまったという反省点があります。
今回の本選は、前述の Conditional GET の挙動が不条理であったという指摘を何名かの参加者から受けており、出題チーム一同、実際にそのとおりであったと反省しております。また、競技序盤に多くの不具合が発生し、十分な競技運営ではなかったことも含め、あわせてお詫びいたします。
もっと ISUCON4
ISUCON4 をまだまだ楽しみ足りない皆様のために、本選の環境を再現できる Amazon AMI を準備中です。近日中に公開予定なので、もう少々お待ちください。
なお、宣伝で恐縮ですが、私 mirakui が主催している Podcast Admins Bar において、 ISUCON4 出題チームの @rosylilly, @sora_h に加えて @941 さんをお呼びし、 ISUCON 4 の裏話を収録したので、よろしければお聞き下さい。
また、 2014/12/10 には LINE 株式会社のカフェスペースで ISUCON Makers Casual Talks : ATND というイベントが開催されます。今回の我々を含む歴代の出題者達が登壇し、ビールを飲みながら語るという会です。これを書いている現在すでに定員に達していますが、年末でキャンセルも発生しやすいと思いますので、諦めずに補欠登録してみてください。
おわりに
過去の ISUCON では参加者として楽しんできましたが、今回は出題側として、貴重な体験をすることができました。至らない我々の出題が競技として成立したのは、ひとえに参加者の皆様が支えてくださったからです。本当にありがとうございました!