ISUCON13参戦記録
ISUCON13に参加したので、その記録を纏めておく。
参加経緯:友人に誘われたため
面子の経験
- 自分:普段はモバイルアプリを作ったり、Pythonのコードを書いたりしている。ISUCONは3回程度参加した経験がある。Goはisuconでしか使わない。
- 友人:ISUCONは1~2回参加している。
- 友人の友人:ISUCON初参戦
事前練習編
本戦の1週間前に全員で集って、当日スムーズに出来るように準備をした。それまでノータッチだった。
やったこと
- (友人宅でやったので)、インターネットと作業場所の確保
- Githubレポジトリの作成
- デプロイスクリプト
- ssh鍵を共有しておく
- Goの開発環境構築
- etckeeperの導入方法確認
- ALPの導入方法確認
- netdata導入方法確認
- MySQLのslow queryとpt-query-digestの導入方法確認
他の参加者の記録を確認。これが結構やってよかった。どのようなことをするべきか参考になるし、知らなかったツールなどを知れた。
今回、New Relicの無料枠をいただいたので、その導入もやってみたが、使いこなせる自信がなかったので本番では使うのをやめた。
ちゃんと導入をすればとても役に立つのは分かる。
しかし、そこまでやるのが大変そうなのと、isuconの場合改善とベンチを繰り返すので、一々過去データを除外するのが大変そうだった。
デプロイスクリプトは以下のようなMakefileを作成した。
(競技中にも必要に応じて弄っていた)
REVISION=$(shell date +%Y%m%d%H%M%S)
SERVICE=isupipe-go.service
HOST1=isucon1
SSH_CMD=ssh isucon@$(HOST1)
LINUX_TARGET_ENV=GOOS=linux GOARCH=amd64
BUILD=go build
DESTDIR=.
build:
cd go && CGO_ENABLED=0 $(LINUX_TARGET_ENV) $(BUILD) -o $(DESTDIR)/isupipe -ldflags "-s -w"
rotate_log:
$(SSH_CMD) sudo mv /var/log/nginx/access.log /var/log/nginx/access.log.$(REVISION)
$(SSH_CMD) sudo mv /tmp/slow.log /tmp/slow.log.$(REVISION)
$(SSH_CMD) sudo nginx -s reopen
$(SSH_CMD) sudo mysqladmin flush-logs
deliver: build
$(SSH_CMD) sudo systemctl stop $(SERVICE)
scp -P 22 go/isupipe isucon@$(HOST1):/home/isucon/webapp/go
$(SSH_CMD) sudo systemctl start $(SERVICE)
$(SSH_CMD) sudo systemctl enable $(SERVICE)
slow_query:
$(SSH_CMD2) sudo pt-query-digest /tmp/slow.log | tee slow_query_$(REVISION).txt
deploy: build deliver rotate_log
本番
最初の放送で概要を知り、動画配信の最適化は難しそうだなと思っていたが、全然関係なかった。
DNSのチューニングも分からんので、結局なんも分からんのは変わらないが。
ちなみに問題名にfinalとついていて、始めて今回予選がないことを知った。
手分けをして以下のことをする。
- デプロイスクリプトを作る
- マニュアルを読む
- netdataを入れる
- alpを入れる
- mysqlだったので、slow queryを入れる
これで11時過ぎになっていた。
ベンチを走らせたら、3300ぐらいだった。
netdataは以下の感じ。legendがないが、黄色がmysqlのcpu使用率
DBがボトルネックになっていそうだなと分かった。
index貼り
(自分がしたわけではないが)
DBを確認をすると、全然indexが貼られていないので、slow queryなどを見ながら適当にindexを貼っていった。
- livestream_tagsに livestream_id indexを張る
- livecomments に、(livestream_id, created_at DESC) index
を貼ったら、4000ぐらいになったらしい。
Icon画像データをDBから剥す
alpを見ると、iconのエンドポイントがリクエスト数も時間も掛っていた。
実装を確認すると、icon画像がDBに直接入っていたので、DBから剥すようにした。
iconは初期データには含まれず、postされて始めてDBに保存される
なので、
- POSTされたらディスクに書き出しておいて
- 他のところで、ハッシュ値が必要だったので、それだけ別テーブルで保存しておいた。
- GETはnginxでサーブするようにした
302を返す処理は一旦無視した。
これで、7000ぐらいになった。
DNSのTTLを設定
DNSはどうボトルネックになっているのか分からなかったが、とりあえずTTLはすぐに設定出来そうだからやってみたけど、効果はなかった。
2台構成に変更
DBが重そうだったので、mysqlを別サーバに移動することに
デフォルトのアカウントがlocalhostだけだったり、環境変数で接続先が上書きされたり、icon剥しで作成したテーブルをもう一方で作成するのを忘れていたりで、結構沼った。
特に最初の問題は気がつかなくて、分離したけど、全然速くならないなとか言っていた。
無事分離した後負荷を見ると、まだDBのCPUが貼りついていた。
livestreamStaticsのN+1の解消
Statics系が結構時間かかっていて重そうだったので実装をみたら、ランキングの集計がN+1になっていたので解消をした。
とりあえず、単一クエリで解消するようにした。(最終的それでも遅かったので、どうしたら良いのだろうか?)
9000ぐらいになった。
この時点で16時過ぎになっていた。
3台構成に変更
PowerDNSもmysqlを使用していて、それもCPUを1コア使用していたので、3台目をPowerDNS用DBにすることにした。
PowerDNSごと分離しても良かったが、少しめんどくさ時間がかかりそうだったので、DBの接続先だけ変更をした。
10000点付近をうろついていて、あまり変化はなかった?
が、app serverのcpu使用率に余裕が出来た。
userStaticsのN+1を解消
友人が手間取っていたので、N+1の解消を手伝った。
これで、15000点ぐらいなった。
themesのuser_idにindexを貼った
大きなN+1が解消したのと、DNSのクエリログが分離されたため、slow queryの結果が変化した。
20000点ぐらいになった。
最後になって改善の速度が上ってきた。
DNS DBのrecordsのnameにindexを貼る
なぜか貼られていなかったで貼っておく。
22000ぐらいになった。
reservation_slotsの検索を、N+1ではなく空きslotがあるかどうかで判定するように
上記の通りだが、無駄ぽい処理があったので、直した
後片付け
各種ログやNetdataを停止した。17:55ぐらいでギリギリになってしまった。
最終スコアは24000ぐらいだった。
感想
今回の問題の核心だろうDNSのチューニングに辿りつかなかったのはくやしいが、基本的な最適化でそこそこ速くなったので、大会自体は楽しめた。(今回、alpとslow queryとnetdataしか見ていない)。
Netdataは今回始めて使ったが、おおざっぱにどこにボトルネックがあるのか分かるので、便利そうだった。(見方を理解していので、全く使いこなせていなかった... 競技中に「なんでこんなにグラフが多いんだ」と愚痴っていた)
まだまだ基礎的な改善をする余地はあったので、一つ一つの改善をもっと素早くやれるようになれば、さらにいけそうだったが、まあ付け焼刃では難しいかろう。
ISUCONは練習をするのが難しいが、参加した大会だとある程度問題が頭に入っているので、他の人のアプローチを見るのが勉強になる。(だから、運営もwrite upを書くまでがisuconです、みたいなことを言っているのだと思う)
他の人に記録を見て勉強しようと思うので、この記録が誰かの役に立てばと願う。