ISUCON13に参加してきました
今回、 @a01sa01toと @Ryoga_exeと私 @sor4chiの 3 人で Maxif. というチーム名で参加してきました。
去年の予選 (ISUCON12q) 開催時に Twitter で知り、今年は絶対に参加したいと思っていたので、参加できてとても嬉しかったです。
結果は 58960 点くらい、総チーム数 694 中 25 位で学生 5 位でした!
初参加にしては健闘できたと思います。
練習
今回は弊学のプログラミングサークル Maximum から 8 人と助っ人の筑波大学の 1 人から 3 チーム (Maxif.、maximum-baby、maximum.mum)を結成し、それぞれのチームで練習をしていました。
練習では全員が同じスペックで環境を構築して対策できるように、さくらのクラウドさんで Terafform Provider SakuraCloud と Cloud Init ISUCON を使って IaC で環境を構築しました。
また、本戦同様に得点推移を確認できるように簡易的なリーダーボードも内製しました。
https://leaderboard.maximum.vc本戦
実際のコードはこちらにあります。
https://github.com/sor4chi/isucon13焦りすぎて点数の正確な記録が一部ないため、以下はおおよその記録だと思ってください。
チーム分担
名前 | 担当の流れ |
---|---|
t@sor4chi | セットアップ → App のチューニング → Nginx/MySQLのチューニング |
t@a01sa01to | 仕様書読む → DNS関連 → サーバー分割 → App のチューニング |
t@Ryoga_exe | 仕様書読む → App のチューニング |
作業ログ
初期点 (3950)
初期点は 3950 でした。
Nginx / MySQL 構成であること、Go 言語で立っていること、sysmtectl | grep isu
、neofetch などお決まりの環境チェックをして、例年の構成と同じだと判断しました。
用意していた Makefile を置いたり、 alp や pt-query-digest 、 discorder などのツールを落としてきました。
livestream_tags
へINDEX貼る (2958)
ベンチマークを走らせてみると、やはりスロークエリが多いようでした。
pt を見ると上位に Rows examine (avg) 10.88k
に対して Rows sent (avg) 3.69
というトンデモクエリがあったので、これに対してインデックスを貼りました。
SELECT * FROM livestream_tags WHERE livestream_id = 7494\G
livestream_tagsへINDEX貼るNOTE
※ 実は貼ったつもりでしたが、貼り方を間違えていて点数が上がらず地獄でした。
同じサークルから別チームで出ていて、練習会で一回も負けたことない maximum-baby と maximum.mum が初っ端から6000点とか出してて「俺ら今回負けるんじゃね!?」なんて話してた記憶があります以降、DB負荷が下がらないまま 3~5000 点付近5時間ほどうろうろすることになります
fillLivestreamResponse
の tags に関するN+1を解消 (3141)
@sor4chiが fillLivestreamResponse
内の tags に関する N+1 を解消しました。ほとんど誤差な気がします。
themes をキャッシュ (3650)
@sor4chiが themes テーブルへのクエリが SELECT
と INSERT
しかないことに気づき、 SELECT
した結果をキャッシュするようにしました。
user icon の hash をキャッシュ (3543)
ユーザーのアイコンのハッシュをキャッシュすることで、DB の負荷を下げました。
この時同時に If-None-Match
を使って、キャッシュが有効な場合は 304 Not Modified
を返すようにしたつもりだったのですが、実際はリクエストの If-None-Match
ヘッダはダブルクォーテーションで囲まれていたため、その Hash 文字列と素の Hash が一致しないためキャッシュが効いていませんでした。
(これは最後まで気づきませんでした)
それから、キャッシュヒットせずにハッシュを生成したあと、ETag
ヘッダに入れないといけないことを知らず、フロントエンドエンジニアとしてとても恥ずかしいことをしました。
(これも気づかなかったのでほとんど意味なかったのかなと思ってます)
サーバー分割 (4504)
@a01sa01toがサーバー分割を行いました。
ついでにベンチ開始後の DNS 追加設定にも TTL を設定するようにここで変更してくれたようです。
サーバー名 | 役割 |
---|---|
S1 | DNS + MySQL (DNS DB) |
S2 | Nginx + App (Web) |
S3 | MySQL (App DB) |
のようにすることで、水責めの負荷が App や App DB に影響しないようにしました。この分割の判断はかなり早かったと思います。
さらに、DNS DB にも Lookup 負荷がかかることに気づき、INDEX を貼っていくれたそうです。
@a01sa01toが表現してくれたフローがこちら。わかりやすい。
ベンチ -[global]-> DNS (s1) -[local]-> DB (s1)
ベンチ -[global]-> nginx (s2) -[local]-> App (s2) -[private]-> DB (s3)
DNS 何もわからんなのでとても助かりました。
サーバー分割getUserStatisticsHandler
のN+1解消 (6409)
@Ryoga_exeが getUserStatisticsHandler
の N+1 を解消しました。
INDEXを正しく貼る (22687)
開始から 5 時間経って、t@a01sa01to がやっぱり INDEX が効いてないんじゃないかと言い出して、実際に MySQL にログインして EXPLAIN
してみると、 livestream_tags
に livestream_id
に対する INDEX が貼られていないことに気づきました。
じゃあもうわからんから 10_schema.sql
に書くのやめようということで Index Already Exists
にならないようにアプリ側でキャッチしながら CREATE INDEX
するようにしました。
ついでに pt-query-digest
上位だった SELECT の Rows examine / sent
比が高いクエリ全てに INDEX を貼りました。
テーブル名 | カラム名 |
---|---|
livestream_tags | livestream_id |
livestreams | user_id |
reservation_slots | start_at , end_at |
ng_words | user_id , livestream_id |
icons | user_id |
ここで点数が 22687 と一気に 4 倍近くまで上がりました。
t@sor4chi「本当にごめん、あとで土下座させていただきます」
getReactionsHandler
の N+1 解消 (28638)
@Ryoga_exeが getReactionsHandler
の N+1 を解消しました。
user icon の hash の余分な計算を削減 (38578)
キャッシュヒットした後でもに DB に image を取りにいくような実装になっていることに気づき、t@a01sa01to がキャッシュヒットした後は DB にいかないようにしました。
userIconの余分なDBアクセスを削減fillLivestreamResponse
の N+1 解消 (45116)
@sor4chiが fillLivestreamResponse
の N+1 を解消しました。
できなかったけどやりたかったこと
- DB の Max Connection 数を増やす (最後の 5 分くらいで 10 に制限されていることに気づいた)
interpolateParams=true
にする (気付いてましたがやるのを忘れてました)
他にもあった気がしますが、大会で相当焦っていたので忘れてしまいました。
感想
INDEX が効いてない間、点数が上がらなくても極端に下がらなければ積極的にマージするような方針で動いてました。
「どこかにボトルネックがあり、そこが解消すると点数が一気に上がるはず」と信じていたので、実際 INDEX を貼って点数が 4 倍になった時はこれまでの努力が報われた気持ちになりました。
ただ、ちゃんと INDEX 貼れているかの確認を怠っていたのは反省点です。ちゃんと Slow Log を監視していれば examine / sent
比が依然として高いクエリがあることに気づいていたと思います。これに気づけていれば 10 万点いけてたかもしれないので本当に悔しいです。
来年は計測力を強化してもっと根拠を持った改善をしていきたいです。
最終的なスコア遷移はこのようになりました。
目標の 30 位圏内に入れて、とても嬉しかったです。
Go 未経験の 3 人で初参加ということで色々苦労した点はありましたが、ISUCON 上位入賞という目標に全力で取り組めて、とてもいい経験になりました!