#isucon2 のお手伝いをしました

昨年は参加者として出場して楽しませていただいた、ISUCON。
#isucon で優勝したチームのメンバーとして参加してた - すぎゃーんメモ

今年は奇しくも開催側の会社に転職したということで、参加者としてではなく運営側としてお題作りなど裏側に関わっての参加になりました。
確か入社前の1月 #bphbqpstudy2012 にて飲んだときにtagomorisさんに「入社したら次のISUCON手伝いますんで」なんて酔っ払いながら言っていたような気がするけど、おかげで準備スタートのときに声をかけていただいて、微力ながら手伝わせていただくことができました。

初期アプリ

まずは何となくの「チケット販売サイト」というお題の方針が決まり、昨年のお題 を参考に叩き台アプリを作らせていただきました。
このへんのネーミングは自分ですね。DISられなくて良かったです。
https://github.com/tagomoris/isucon2/blob/4cc32693905ae6cb9fbc39c1af4e5e6f280131f0/webapp/config/database/initial_data.sql#L10


最初にPerlアプリを、まず昨年と同じくKossy を使って一通り各ページと購入処理までできるよう書きました。今回はトランザクション処理も必要だしちょっとSQL発行も多く、生DBIよりモジュール使った方がいいかな、とDBIx::Sunny も使用することにしました。
Kossyはサイドバーみたいなのを実装するときにfilterを簡単に利用することができて便利ですね。


で、ある程度のアプリ仕様が固まってきたところで他の言語も用意し始めました。自分は一応Node.js, Rubyもこれくらいなら出来そう、ということでNode.js版Ruby版 も担当させていただきました。Python版、PHP版、Java版はそれぞれ社内の別の方に書いていただいて、その後の修正などに幾らか手を入れたりさせていただきました。

調整

ある程度揃ったところで、並行してid:tagomorisさんが作っていた負荷ツールやチェッカーも出来てきて、それを使ってそれぞれの言語でちゃんと動くか確かめます。
その後も、チェッカーで使うためにDOMを変更したり、より意地悪な問題になるように仕様を変更したり、でPerl版はちょいちょい変更がかかります。それに追従して他言語でも同じSQLを発行して同じDOMを吐くようテンプレートを修正して…とかはなかなか面倒でしたね。。複数の言語で同じ動作をするアプリを作る、というのはなかなかこういうところが大変だと思いました。
静的ファイルは統一してシンボリックリンクの先を参照するようにしていたのでラクでしたが。

デプロイ準備

で、試験用にサーバを用意していただいたタイミングで各言語をappサーバの5000番ポートで動くよう設定します。
前回同様、supervisord を使って起動・終了できるように。
https://github.com/tagomoris/isucon2/blob/4cc32693905ae6cb9fbc39c1af4e5e6f280131f0/webapp/etc/supervisord/isucon.conf

Perl

のみ使用。cartonとか使わずにcpanmでextlibに入れてパスを通す形で、Starman でplackup起動。

Node.js版
  • express
  • jade
  • mysql
  • async
  • moment

昨年のものに倣ってexpress を使用して作りました。だいぶバージョンも書き方も変わったとは思うけど。
MySQLに続けてクエリを投げる処理が多く、どうしても普通に書いているとネストが深くなりがちだったので、止むを得ずasyncを多用しました。series, waterfall, mapなど、それぞれ使いやすく書く分には便利ですね。それを使わずに正直に書いたらもっと早くなったりするのかな…?ちょっと分かりません。
デプロイではclusterを使って複数プロセスで動かすようにしました。
今は手元で再現できていないのですが、supervisordから起動して終了させたときに子プロセスが生き残ってしまうという現象が発生し、よく分からなかったのでsupervisordの設定で

stopsignal=QUIT

として無理矢理ころすようにしました。あとで教えていただいたのですが、下記のようにシグナルをちゃんと処理するようにするのが正解のような気がします。
supervisor配下でcluster.jsを動かすためのTips - アルパカDiary

Ruby

昨年のものに倣ってSinatra を使用。テンプレートエンジンは個人的な好みでhamlではなくslimに。開発中はshotgunがとても便利でした。

実際のデプロイにはUnicorn を使用。最初はThin でやっていたのだけど、Starmanみたいに複数workerで起動するようなのが出来ない(?)ようだったので、unicornを選択。
で、手元で実際にworker_processes 50で立ち上げてベンチマークを動かしてみたところ、

Mysql2::Error - Too many connections:

というエラーが多発。mysqlのconnectionはリクエスト毎に破棄され切れてくれると思うのに何故?と思ってググってみたところ、
http://unicorn.bogomips.org/Unicorn/OobGC.html
Unicornはデフォルトで5 request毎にGCが走るようになっているっぽい、ということで試しに

require 'unicorn/oob_gc'
use Unicorn::OobGC, 1, %r{\A/}

として1 request毎に走るようにしてみたところエラーが出なくなったのでコレでいいかなーと思ってこうしました。
普通にMySQL側の設定でmax_connectionsの値を大きくすれば問題ないのかも知れませんが。

(参照: #isucon2 に参加しました - @kyanny's blog)

Python

詳しくは知らないのですが、flaskというのを使ってWSGIなアプリを作っていただいたので、gunicornというサーバで動かしてみました。



それぞれの言語について専門家の方々から「もっとこうして作ればいいのに!」とかツッコミあればご指摘いただけると嬉しいです。

初期状態スコア

そんなわけでセットアップした各言語で動かして前日夜に初期状態でスコアを取ってみたのが以下になります。

perl:   837 tickets, score:587233
ruby:   440 tickets, score:1117082
node:   102 tickets, score:4818816
python: 466 tickets, score:1054754
php:     90 tickets, score:5461316

ちょっと怪しいのでミスっていたかもしれません!あとでもう一回とりなおしてみるつもりです!

#ISUCON2 を終えて

コンテスト当日は所用があって昼過ぎで退席したので最後まで見届けることが出来なかったのですが、Twitterやブログでとてもエキサイティングに楽しんでいただけた様子を知ることができました。
ブログで作業メモや考えたこと、反省などを書いている方が多く、思考を追うことができて非常にありがたいです。ありがとうございます。
livedoor Techブログ : #isucon2 参加者・関連エントリまとめ


あれこれ考えながら相談しつつお題を調整し、作っていくのはとても楽しかったです。参加者のみなさまにも楽しんでいただけたようで何よりです。
来年どうなるか分かりませんが、また何らかの形で関わりたいな、と思います。

ありがとうございました。