Gマイナー志向

とくに意味はありません

第4回ISUCON予選にチーム「ご注文はPHPですか?」で参戦して1日目暫定10位になりましたがPHP使ってません

いい感じにパフォーマンスチューニングするコンテスト第4回ISUCONに参戦しました。まだ現時点で本戦に進めるのかわからないのですが、1日目で暫定10位になりました。

PHPでも十分に戦える!と思った方、ごめんなさい。Go言語使いました。

チーム紹介

チーム名
ご注文はPHPですか?
チーム略称
ごちぺち
予選スコア
44000〜45000ぐらい(暫定)
メンバー
アイコン https://pbs.twimg.com/profile_images/1536770974/square1024_2_normal.jpg https://pbs.twimg.com/profile_images/497897656308350976/wKvhLHq8_normal.jpeg https://pbs.twimg.com/profile_images/2704710656/b3e622404badbefd22b2e516bed625d2_normal.png
Twitter @do_aki @matsuu @netmarkjp
職業 インフラエンジニア インフラエンジニア インフラエンジニア
得意言語 PHP Perl Python
当日の役割 右腕 左腕 ファシリテータ

えぇ、ぺちぱー(PHPer)で有名なdo_aki先生がいるチームですが、Go言語を使いました。

役割分担大事。特にファシリテータ大事です。fujiwara組もたぶんfujiwaraさんがファシリテータだと思います。

Go言語を選んだ理由

ノリと勢い

予選の9日前(9/18)に顔合わせ&作戦会議*1で「どうせならGo言語で出てみる?」となったので。

Go言語の勉強について

Go言語の経験はほとんど無く、9/18からA Tour of Goを始めた程度の素人。
9/20あたりから前回のISUCON3予選のAMIに含まれてるGo実装でチューニング実践してみた程度。三人ともISUCON出場経験がはあったのでチューニングはなんとかなりました。ISUCON3予選問題のチューニング結果は50000ぐらい。Go言語つええええ。

ISUCON3の予選問題はGo言語の勉強に最適ですよ奥さん。

今回の予選の対応方法

基本的にnginxのアクセスログとにらめっこです。

アクセスログ応答時間を出力するようにして、リクエスURIごとのトータル時間、アクセス回数、平均応答時間を算出する簡単なスクリプトを用意。その数字をみて、どのリクエストを改善すべきかを決定していきました。

いきなりソースを見たり、topやdstatなどでボトルネックを調べたりしたくなるんだけどもそこはグッと抑える。これは過去にISUCONに出た経験から導き出されたやり方でした。

ソースの編集について

幸い全員Vim使いで、ターミナル上で編集することに抵抗がないので、3人とも1台のインスタンスSSH接続、役割分担しながらプログラムや設定などを書き換えていく感じでした。
BitBucket上のgitにリポジトリを作りましたが、そこからデプロイ&継続的インテグレーションの類は使ってません。サーバ上で編集したものを同じユーザ名でcommit&pushした程度です。

また、リポジトリを一応用意しましたが、綺麗に保つ努力はしませんでした。時間がもったいないから。main.go.hogeファイルとか平気で作ってました。短時間決戦なので気にしない。

どんな実装になったか

ミドルウェアはGoに変えた以外殆ど変わってません。nginx+Go+MySQLです。NginxとMySQLのバージョンアップもしませんでした。

できるだけ前の方で返すべく以下のような実装にしました。

  • スタイルシートや画像ファイルなどの静的ファイルはnginxから返す
  • ログイン失敗でbanされたIP/ユーザをチェックするためにlogin_logをSELECTしている部分は、ログイン失敗した回数をGo上でカウント、banされたリストもGo言語上で保持してMySQLを参照しない*2
  • usersテーブルは初回のデータ投入以降増えないので、初回に全部Go実装上にキャッシュしてGoで返す
  • 最終ログイン日時もユーザ毎にGo上で記録しておき、Go上で返す
  • トップページは出力パターンが4パターンしかないので、4パターンの静的HTMLファイルを生成しておき、nginxでCookieの値を見てrewriteして静的ファイルを返す

それぞれの修正を適用するたびにアクセスログ応答時間を見て遅いリクエストを改善していく感じですね。

結果としてこのような対策になったので、今回MySQLのチューニングは殆どやってません。my.cnfの設定も大した設定は追加してないし、インデックスも結果的には何も追加しませんでした。

なお、プログラム以外の具体的な変更内容はnetmarkjp先生がブログに詳しくかかれているのでそちらをご参照ください。

便利情報

いくつかピックアップ

BitBucketは無料でプライベートなgitリポジトリを作れる

情報をもらさずにリポジトリを共有できて便利

BitBucketのプライベートなgitリポジトリと連携できるCIはWerckerが便利

wercker、便利ですよ!今回使ってないけど。boxにwercker/golangを使えばGo1.3系をにも対応してます。

Vimで同じファイル複数人で開きたいけどreadonlyでいい場合は-Rオプションが便利

vim -Rで最初からreadonlyで開くことができるので邪魔せずに便利

mysqltuner.plが便利

MySQLのざっとしたチューニングはこれを少し参考にしてます。

curl http://mysqltuner.pl > mysqltuner.pl
perl mysqltuner.pl

でも今回は殆ど使わなかった。

iotopコマンドが便利

disk ioを発生させているプロセスが何かわかります。

yum install -y iotop
iotop -o
dstatのオプションは-tlamp -N loが便利

今回は1台構成でloしか使わないので-N loをつけると便利です。

yum install -y dstat
dstat -tlamp -N lo

loの通信量が多くなれば、おそらくたくさん捌けてるんだな(スコアがあがるかもな)と判断できます。

Goのつらかったこと

いくつかピックアップ

経験が浅い

基礎的なことを把握してなかった。例えばinit()ってどこでどう呼ばれるのか知らなかった。

使えるプロファイラがない

pprofはCPU時間ベースのプロファイリングやchannelによるblock頻度を出力できるものの、Go以外の理由(mysqlなど)で待ちになった場合も含めて簡単にプロファイリングする術を見つけられませんでした。

なんとかNitroなるものを見つけてこれをtimer代わりにStepで調査してたのですが、Goroutineを使いはじめると破綻するし、アクセスが増えた場合の調査が難しいので結局あまり使えず。いいソリューションないですかね。

まとめ

よく言われる「推測するな、計測せよ」ですね。
dstatやtopを見てると計測してる気になるんですが、計測すべきはスコアに直結する応答時間でした、って感じです。

*1:作戦会議という名の飲み会

*2:login_logは最初からデータが入っているのでそのデータもキャッシュしておく必要がある