ISUCON5 の出題担当の一人、tagomorisです。みなさん予選はいかがでしたか? 楽しめましたか?
今回の準備をするにあたり、参考実装の準備などについて多くの協力を @najeira さん、 @hydrakecat さん、 @making さん、 @taroleo さんにいただきました。多くのみなさんが参加できたのはひとえにこの方々の協力があってこそだと思います。特に @najeira さんには予選直前の土壇場での動作確認・修正など非常にお世話になりました。本当にありがとうございました。決勝の準備でも、できればこれに懲りず、よろしくお願いします。次こそは余裕をもって準備します :P
また共犯者というかメイン出題担当のもう一人 @kamipo さんにも大きな苦労をかけました。いつもすまないねえ……。
今回の予選はいろいろと不手際が多く、特にNode.js実装が土壇場で用意できないということになるなど、申し訳ない点が多くありました。こういった点は全て、自分の最初の準備作業の遅れと不手際によるものです。この場を借りてお詫びします。申し訳ありませんでした。
さて、ISUCON5予選に使用した全てのリソースを含むリポジトリを、このエントリ公開と同時に公開します。
https://github.com/isucon/isucon5-qualify
問題内容についての講評は別エントリにてkamipoさんに任せるとして、自分からはサーバのプロビジョニングとベンチマークツールについて簡単な説明をしようと思います。
Google Cloud SDKがインストール済みであること Ansible Ruby 2.3 (+ 各ディレクトリで bundle install)
gcp/image や gcp/bench などのディレクトリで bundle exec ruby ./pack.rb run_instance --name machine-name すればインスタンス起動、おなじく ruby ./pack.rb provision すれば環境構築とコードのデプロイ、ビルドなどが走ります。インスタンス作成時の設定については基本的に予選時のものがデフォルトで決められていますが、オプションで変更することもできます。
綺麗にやるならGoogle Cloud Platform APIを叩いてインスタンス生成などもやるべきでしたが、とりかかった時点ではRuby用ライブラリの出来が微妙そうだったので回避して gcloud コマンドを経由して実行することにしました。このコマンドは --format json を指定することで結果をJSONで取得できるのが、プログラムからも扱いやすく便利で良いですね。バージョンによってちょいちょいサブコマンド体系が変わるのが難点ですが……。 ISUCON予選開催日の少し前にGoogle Cloud Platform API全般を扱うRuby用の新しいライブラリがリリースされたのを見ましたので、現在はもうすこしAPIを直接操作する方法でも綺麗にやれるのかもしれません。
またISUCON4の段階ではイメージ作成も pack.rb に組込まれていましたが、これは自分がとりかかった時点ではGCE上で自動化することが事実上不可能だったため、手順をコメントで示すだけにしてあります。実際には手作業で行いました。これも現在の Google Cloud SDK では新しいイメージ作成用のサブコマンドが追加されているようなので、今ならもっと綺麗に実現できるだろうと思います。
結果的にはこれらの狙いはそれなりにうまく実現できたように思います。それなりに高スループットなHTTPリクエストを送りつつ、以下のようなDSLで記述されたロジックでアプリケーションの動作チェックも行ったりしていました。(感想ブログエントリに散見されたタマキ出現のチェック失敗はおそらく以下のコードでのチェックにひっかかっていたもののはずです)
予選と同じベンチマークは bench/ 以下で gradle run すると 127.0.0.1 に向けて実行されます。 実行されるのは Isucon5Qualification.java というシナリオで、これは複数のシナリオを3ステップに分けて実行する抽象シナリオとなっており、実際には Isucon5InitExecutor (/initialize を呼び指定の時間内にリクエストが戻るかをチェックする)、BootstrapChecker (アプリケーションの大まかな動作チェックを行い) の2ステップと本番の負荷走行 + 並行したチェック処理の計3ステップを順次実行します。無条件に1分かかる本番の負荷走行を実行してしまうと全く動作していないアプリケーションが相手でも1分以上結果を待たないといけなくなるため、最初の2ステップのどちらかで失敗した場合には即座に停止するようになっています。
各ステップでは並列に動作させるシナリオ数を任意に指定することができるのですが、今回負荷走行時の並列シナリオ数は4でした(全力の負荷走行3スレッド、レスポンスをチェックしながらの走行1スレッド)。もっと並列度を上げることもできたのですが、今回は参考実装の初期状態が非常に重く、ベンチマークツールの負荷をあまりかけてしまうとそもそも初期状態でのチェックが全く通らなくなってしまうためです。 このためフルチューンされたアプリケーションを相手にするには並列度が多少足りず、予選の最上位の数チームの環境では、いくらか負荷がかけきれなかったケースもあったようです。このあたりは決勝に向けては更に改善していこうと考えています。
個人的には、1週間足らずで作った割にはそこそこよくできたかなと……。まだ一般的に使うには機能があれこれ足りないと思いますが、決勝が終わったら個別のツールとして切り出して公開したいと思ってます。
各エージェントプロセス(bench/agent.rb)はインスタンスを起動したらそのままsystemdから起動され、即座に動きはじめます。このためキューが詰まり気味になってきたらノードを追加すればすぐに対応が可能となっており、GCEのインスタンス起動の早さもあいまって、それなりに快適な状況を維持できたのではないかと思います。2日で11500回ものベンチマークを回すイベントはなかなか無いのではないでしょうか。それもこれも潤沢なリソースをGoogle様にご提供いただけたおかげだと思います。
みなさんが手元でこの分散ベンチマーク環境を再現することも可能ですが、bench/agent.rb などには予選時のポータルサーバのIPアドレスなどが直書きしてありますので、そういったあたりに気をつけていただければと思います。 eventapp 以下にある予選ポータルサーバも当日の時刻などを用いて参加者がログインできる時刻の制御を行ったりしていますので、そのあたりにもご注意ください。
なおこのあたりの一式も、おそらくもうすこし改良を加えつつ決勝で使うことになるだろうと思っています。決勝ではチーム数も少ないですから時系列での得点グラフも欲しいし、応援する方々のために、参照のみなら誰でも可能なようにしたいところですね。
今回の準備をするにあたり、参考実装の準備などについて多くの協力を @najeira さん、 @hydrakecat さん、 @making さん、 @taroleo さんにいただきました。多くのみなさんが参加できたのはひとえにこの方々の協力があってこそだと思います。特に @najeira さんには予選直前の土壇場での動作確認・修正など非常にお世話になりました。本当にありがとうございました。決勝の準備でも、できればこれに懲りず、よろしくお願いします。次こそは余裕をもって準備します :P
また共犯者というかメイン出題担当のもう一人 @kamipo さんにも大きな苦労をかけました。いつもすまないねえ……。
今回の予選はいろいろと不手際が多く、特にNode.js実装が土壇場で用意できないということになるなど、申し訳ない点が多くありました。こういった点は全て、自分の最初の準備作業の遅れと不手際によるものです。この場を借りてお詫びします。申し訳ありませんでした。
さて、ISUCON5予選に使用した全てのリソースを含むリポジトリを、このエントリ公開と同時に公開します。
https://github.com/isucon/isucon5-qualify
問題内容についての講評は別エントリにてkamipoさんに任せるとして、自分からはサーバのプロビジョニングとベンチマークツールについて簡単な説明をしようと思います。
サーバプロビジョニング
ISUCON5の準備にとりかかる前に ISUCON4のリポジトリ を見たところ、これのサーバプロビジョニングまわりが非常によく整備されており、今回はこれをかなり流用させていただきました。具体的にはISUCON夏期講習2015用のGCEイメージ作成でまず使用し(ただしこのときはCentOS)、そののちUbuntu 15.04用に書き換えつつISUCON5予選で用いています。 動作環境としては以下のようなものになります。gcp/image や gcp/bench などのディレクトリで bundle exec ruby ./pack.rb run_instance --name machine-name すればインスタンス起動、おなじく ruby ./pack.rb provision すれば環境構築とコードのデプロイ、ビルドなどが走ります。インスタンス作成時の設定については基本的に予選時のものがデフォルトで決められていますが、オプションで変更することもできます。
綺麗にやるならGoogle Cloud Platform APIを叩いてインスタンス生成などもやるべきでしたが、とりかかった時点ではRuby用ライブラリの出来が微妙そうだったので回避して gcloud コマンドを経由して実行することにしました。このコマンドは --format json を指定することで結果をJSONで取得できるのが、プログラムからも扱いやすく便利で良いですね。バージョンによってちょいちょいサブコマンド体系が変わるのが難点ですが……。 ISUCON予選開催日の少し前にGoogle Cloud Platform API全般を扱うRuby用の新しいライブラリがリリースされたのを見ましたので、現在はもうすこしAPIを直接操作する方法でも綺麗にやれるのかもしれません。
またISUCON4の段階ではイメージ作成も pack.rb に組込まれていましたが、これは自分がとりかかった時点ではGCE上で自動化することが事実上不可能だったため、手順をコメントで示すだけにしてあります。実際には手作業で行いました。これも現在の Google Cloud SDK では新しいイメージ作成用のサブコマンドが追加されているようなので、今ならもっと綺麗に実現できるだろうと思います。
ベンチマーク
最初はいろいろサボってシンプルに作ろうと考えていたのですが、迂闊なことに今回の問題がセッションを扱えるツールでないと何もできないことをすっかり忘れていました。これに気付いてからどうするか真剣に考えたのですが、自分の実装能力と慣れ、新しいものにトライしたいという気分、性能面などを考慮した結果、えいやとJava8でフルスクラッチしました。高スループットで処理を走らせつつ、ベンチマーク時のチェック内容の記述性などを Java8 Lambda を用いた内部DSLで担保するというのが狙いでした。結果的にはこれらの狙いはそれなりにうまく実現できたように思います。それなりに高スループットなHTTPリクエストを送りつつ、以下のようなDSLで記述されたロジックでアプリケーションの動作チェックも行ったりしていました。(感想ブログエントリに散見されたタマキ出現のチェック失敗はおそらく以下のコードでのチェックにひっかかっていたもののはずです)
getAndCheck(session, "/friends", "LIST OF FRIENDS", (check) -> {
check.isStatus(200);
check.content("#friends dd.friend-friend a", param2.nickName);
check.contentMissing("#friends dd.friend-friend a", param3.nickName);
});
予選と同じベンチマークは bench/ 以下で gradle run すると 127.0.0.1 に向けて実行されます。 実行されるのは Isucon5Qualification.java というシナリオで、これは複数のシナリオを3ステップに分けて実行する抽象シナリオとなっており、実際には Isucon5InitExecutor (/initialize を呼び指定の時間内にリクエストが戻るかをチェックする)、BootstrapChecker (アプリケーションの大まかな動作チェックを行い) の2ステップと本番の負荷走行 + 並行したチェック処理の計3ステップを順次実行します。無条件に1分かかる本番の負荷走行を実行してしまうと全く動作していないアプリケーションが相手でも1分以上結果を待たないといけなくなるため、最初の2ステップのどちらかで失敗した場合には即座に停止するようになっています。
各ステップでは並列に動作させるシナリオ数を任意に指定することができるのですが、今回負荷走行時の並列シナリオ数は4でした(全力の負荷走行3スレッド、レスポンスをチェックしながらの走行1スレッド)。もっと並列度を上げることもできたのですが、今回は参考実装の初期状態が非常に重く、ベンチマークツールの負荷をあまりかけてしまうとそもそも初期状態でのチェックが全く通らなくなってしまうためです。 このためフルチューンされたアプリケーションを相手にするには並列度が多少足りず、予選の最上位の数チームの環境では、いくらか負荷がかけきれなかったケースもあったようです。このあたりは決勝に向けては更に改善していこうと考えています。
個人的には、1週間足らずで作った割にはそこそこよくできたかなと……。まだ一般的に使うには機能があれこれ足りないと思いますが、決勝が終わったら個別のツールとして切り出して公開したいと思ってます。
分散ベンチマーク環境
分散ベンチマーク環境はかなりシンプルで割り切った作りになっており、キューは予選ポータルサーバ上のMySQLの1テーブル、各エージェントノードはそのテーブルを持つMySQLに直接接続するという生々しい構成です。綺麗にやるならWeb API経由にするでしょうが(ISUCON2でやったときはそういう作りだった)、綺麗にやってもあんまりメリットがないんですよね、ISUCONだと……。各エージェントプロセス(bench/agent.rb)はインスタンスを起動したらそのままsystemdから起動され、即座に動きはじめます。このためキューが詰まり気味になってきたらノードを追加すればすぐに対応が可能となっており、GCEのインスタンス起動の早さもあいまって、それなりに快適な状況を維持できたのではないかと思います。2日で11500回ものベンチマークを回すイベントはなかなか無いのではないでしょうか。それもこれも潤沢なリソースをGoogle様にご提供いただけたおかげだと思います。
みなさんが手元でこの分散ベンチマーク環境を再現することも可能ですが、bench/agent.rb などには予選時のポータルサーバのIPアドレスなどが直書きしてありますので、そういったあたりに気をつけていただければと思います。 eventapp 以下にある予選ポータルサーバも当日の時刻などを用いて参加者がログインできる時刻の制御を行ったりしていますので、そのあたりにもご注意ください。
なおこのあたりの一式も、おそらくもうすこし改良を加えつつ決勝で使うことになるだろうと思っています。決勝ではチーム数も少ないですから時系列での得点グラフも欲しいし、応援する方々のために、参照のみなら誰でも可能なようにしたいところですね。