ISUCON5予選でスコア19450出しました
サーバチューニングの勉強になればとISUCON5の予選に参加してみたところ、予想外に予選通過できてしまいました。 初めてISUCON参加してみての感想を一言で言うと、めちゃくちゃ疲れるけど楽しかったです。 参考までに、僕が行った作業内容を書いてみます。
ベンチマークの実行を成功させる
まずベンチマークを実行できるところまで持っていきます。ここまではなんとかがんばるしかないですね。
mysql, nginx,unicornの設定チューニングする
前回参加者のブログを参考に、あらかじめ準備していたチートシート通りに設定しました。 systemdの使い方がわからなくてサービスを再起動方法をググったりして、わりと手間がかかりました。
設定内容は以下のとおり。
my.cnf
innodb_buffer_pool_size = 2G innodb_flush_log_at_trx_commit = 2
nginx.conf
worker_processes 5; worker_rlimit_nofile 4096; log_format ltsv 'host:$remote_addr\t' 'vhost:$http_host\t' 'port:$server_port\t' 'time:$time_iso8601\t' 'method:$request_method\t' 'uri:$request_uri\t' 'protocol:$server_protocol\t' 'status:$status\t' 'size:$body_bytes_sent\t' 'referer:$http_referer\t' 'ua:$http_user_agent\t' 'forwardedfor:$http_x_forwarded_for\t' 'forwardedproto:$http_x_forwarded_proto\t' 'apptime:$upstream_response_time\t' 'reqtime:$request_time'; #access_log off; access_log /var/log/nginx/access.log ltsv;
unicorn
プロセス数を5に
プロセス数はあとで増やすなど試してみたんですが、効果がなかったので5に戻しました。 ログ解析をするために、ltsv形式で出力しようというのは事前に決めてました。
ここまでである程度スコアが上がったのを確認して、次に戦略を立てるためにログ解析の準備をしました。
ログ解析の準備をしてログ解析する
sonotsさんが前回のISUCONでltsvのログをスクリプトで整形しと書いてたので、このアイデアをそのまま使わせてもらいました。
https://gist.github.com/sonots/9ae0167d31d7f4b42c9a
実際の作業ではボトルネックになってる箇所を見やすいように、URLをグループごとに見れるように、スクリプトを調整しました。
あとはアクセスログを退避してクリアするスクリプトも作っておきました。 これでベンチマークを動かすごとに、詳細なログ解析が得られるようになりました。 これを見てると / へのアクセスの処理にほとんどの時間がかかってて、静的ファイルへのアクセスはほとんど影響してないことがわかりました。なので今回の問題は、静的ファイルを素早くさばくことよりアプリケーションの処理を高速化するのが効果的だろうと判断しました。 このログ解析の仕組みは最後までチューニング方針の決定に役立ってくれたので、準備しておいたかいがありました。
アプリケーションのチューニング内容
アプリケーションのチューニングでやったことは、とくに大きな構成の変更はせず、参考実装のボトルネックを潰していった感じです。 N+1問題を潰したり、mysqlのindexを追加して行ったりしたら順調にスコアが上がっていってくれました。 今回の課題はアプリケーションのボトルネックを潰すのがメインだったので、アプリケーションエンジニアの自分にとっては普段やってる仕事と同じような感じで作業していけました。
反省点
- shellやエディタが普段の環境とちがうまま作業してしまって非効率でした。dotfileをgithubに置いておいて適用したほうがよかった。
- N+1問題が起こってるところをgrepで見つけていたのが良くなかったので、N+1問題を検出する仕組みを準備しておくべきだった。
- kazeburoの術によるとnginx設定はまだ足りないものがある感じ。( https://gist.github.com/sonots/62179703d429d36b1a57 )本選までに勉強しておこう。
良かった点
- ログ解析ツールが役に立った stat.rbは役に立った。実行結果を分析できるので、効率的にボトルネックに対処できた。
- mysql, nginxのチートシートは役に立った
- 予習したおかげで、設定ファイルの場所などで戸惑わずにすんだ。
作業ごとのスコアの遷移を掲載しておきます。 だいたいアプリケーションの修正とindexの追加に時間を使っていて、順調にスコアが上げれてましたね。
12:00くらい 初回ベンチマーク実行 => スコア 146 12:30 * nginx worker process を5に * unicorn worker process を 5に => 881 12:35 my.cnf修正 innodb_buffer_pool_size = 1G innodb_flush_log_at_trx_commit = 2 => 889 13:01 my.cnf修正 innodb_buffer_pool_size = 2G => 911 13:12 vimrcをいつも使ってるサーバからコピペ。vimプラグインは面倒なので持ってこなかった。 13:30 ログを解析したところ / に時間がかかってることがわかったので /を高速化する 13:40 footprints, relationsにindex追加 => 2248 14:19 is_friend? をone -> anotherの向きのみ見るように修正 => 2762 14:30 tail -f してたのをやめた => 2911 14:55 friend_idsを最初に取っておくように => 4352 15:06 comment取得にもfriend_idsを使うように => 4828 15:43 comment取得をsqlだけで取得するように。commentsにindex追加 => 12413 16:04 /friends のSQLをor使わないように修正 => 12432 16:17 /friendsをリファクタリング => 12480 16:26 nginx, unicornのworkerプロセスを5 -> 10に => 12313 効果ないので8に変更 => 効果ないので5に戻した 16:55 footprintのindexをuser_id, owner_id, created_at に変更 => 12460 17:08 一部erubis => slimに変更 => 見た目には正しく変換できてるように見えるがbenchmarkerでエラーになるので中止。 17:46 トップページでfriendsがcountしか使われてなかったので、sqlでcountを取得するように修正 => 13145 18:06 get_userが何度もviewから呼ばれてるので、開始時に取得するように修正(途中) => 14468 18:17 viewからentryを何度もSQLで取得してたので、一括して取得するように修正 => 15476 18:31 /entriesでコメント数を取るために何度もSQL実行してたのを一括して取得するように修正 => 16704 18:45 /footprints, /friendsでget_userを何度も実行してたのを一括して取得するように修正 nginxのaccess log をoffに => 19450