isucon11予選に参戦してきました
「何もしてないのに壊れました」というふざけたチーム名で会社の同期(kypくん、shikiさん)とisucon11の予選に参戦しました。 使用言語はGo、結果は42525点、93位/約600チームで予選敗退でした。
前日までにやったこと
チームで1回だけ役割分担・作業方針を確認しました。私のチームでは次のような方針を立てていました。
- おそらく3台のマシンが与えられるので、最初は各自が1台ずつ自由にデプロイ・ベンチをとって良いスコアを目指す
- いい感じのスコアができたらPR出してマージ
- 1台構成で限界が見えてきたら複数台にして、appとDBを分ける
全員が独立してデプロイ・ベンチができるというのがポイントで、自由に色々といじったりデバッグしたりする時間が取れて良かったです。デプロイはLinux向けにクロスコンパイルしたGoのバイナリをscpでデプロイするスクリプトを書いてました。
チームでの方針確認以外には各自で対策してねといった感じで、個人的にはMySQLのトランザクションモデルを叩き込んだり、インデックスの張り方を復習したり、過去問対策をしていました。
当日
-20分
全員が最も心配された起床試験を無事合格し、公式のライブ動画を見るところから開始でした。今年はISUCONDITIONというWebアプリがテーマで、全国の椅子の状態が大量に送信されてくるので、それを確認できるダッシュボード付きのアプリケーションを高速化しようといったものでした。
0分
開始と同時にマニュアルの重厚さにやや圧倒されつつチームでざっと読み合わせをしました。ここを疎かにすると失格になったりするので気をつけました。特に、AWSのセキュリティグループを変更してはいけないというルールは重要でしたね。
+30分
最初のベンチで2000点くらいでした。ここまでやったことは以下
bind-address = 0.0.0.0 key_buffer_size = 1G disable_log_bin slow_query_log = 1 slow_query_log_file = /var/log/mysql/mysql-slow.log long_query_time = 0
- Nginxのログ設定
ベンチマークをとった後にkataribe + access.logでAPIごとのレスポンスタイムを、pt-query-digest + mysql-slow.logで全クエリの計測結果を集計して、チームのDiscordに投げていました。
Discordに投げるのは去年isucon10で同じチームだったayumin君が作ったlog2discordを改造して使っていました。
- SQLログ出力
go-sql-proxyを使ってアプリケーションログにSQLを出力させるような設定をしました。今回はそれほど見ることはありませんでしたが、APIごとに発行されているSQLが分かって便利です。
+2時間
とったベンチマークをヒントにしつつ、全員で怪しそうな関数やその改善案を出し合ったり、マニュアルを読み込んだりしていました。
まず、dstatの結果から明らかに書き込み(POST /api/condition/*
)が多いことがわかりました。全国の椅子からコンディションが送られてくる(という設定)なので当然ですね。さらにソースコードを見るとこんな面白いTODOが書いてあり、負荷を下げるため9割のリクエストを落としていました。
// TODO: 一定割合リクエストを落としてしのぐようにしたが、本来は全量さばけるようにすべき dropProbability := 0.9 if rand.Float64() <= dropProbability { // c.Logger().Warnf("drop post isu condition request") return c.NoContent(http.StatusAccepted) }
ここの書き込みをMariaDBではなくRedisに逃して、MariaDBには遅延書き込みするといった案が出ました。
また、スロークエリに以下のようなクエリがあったので、実行計画を見つつisu_condition
の(jia_isu_uuid, timestamp)
にインデックスを貼りました。これだけで一気にスコアが18000くらいになりました。
SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = '<uuid>' ORDER BY `timestamp` DESC LIMIT 1
+4時間
kypくんが/api/isu
のN+1の修正、shikiさんがメモリに載せれそうなところをオンメモキャッシュに、私がPOST /api/condition/*
の対応を担当してもくもくしていました。
書き込みのRedis移行に関しては知識が貧弱だったため断念し、とりあえずbulk insertすることで対応しました。kypくん、shikiさんともに実装をマージしてもらいこの時点でスコアは20000~30000くらいでした
+6時間
POST /api/condition/*
は非同期で書き込めるということだったので、リクエストごとではなく、1秒ごとにbulk insertするように変更しました。またこの頃になるとappのCPU使用率が支配的になっていたので、appサーバ2台 + DBサーバ1台の構成に変更しました。
この間にshikiさんがgenerateIsuGraphResponse
の取得件数を全件→1日分だけに絞るようなクエリを書いてくれたり、kyp君が外部APIで使っているhttpクライアントを差し替えたりしてくれました。これらの変更により大体40000点くらいまで上がりました。
一瞬だけYouTube Liveで実況されていたランキングにチームが乗り、チームが沸きましたw
+7時間
この辺りでスコアボードが凍結されましたが、ポータルの障害でベンチが取れない時間が続きました。競技時間が若干延長されるということでやり残していた修正を行いました。
GET /api/trend
のN+1修正POST /api/isu
がINSERT→UPDATEとなっているのをINSERT1つにまとめ、トランザクションを消す- ただしこれは時間の関係でマージできなかった
+8.5時間
競技終了が近くなってきたので再起動試験を行おうとしたのですが、なぜがAWSコンソールが重くてなかなかインスタンスが終了、起動しませんでした。さらに1度目の再起動ではDBサーバへの接続でミスがありアプリケーションの起動がうまくいってませんでした。やばいやばいと言いながら修正したものの、もう一度再起動試験をする時間はありませんでした(この時点で残り2分くらい)。結局、「統合テストはパス!ぶっつけ本番で行くわよ!!」というエヴァでありがちな方針をとったところでタイムアップとなりました。
競技を終えて
運営による再起動試験は運よくパスしていましたが、普通に危ないのでちゃんと時間をとってやりましょうというのが感想というか教訓です(それはそう)。
予選落ちではあったものの、何もできずに惨敗した去年よりは成長を感じられて良かったです。来年はソロで出て自分一人の力試しするのもアリかもなーと思いました(ただ、チームで出るのもワイワイできて楽しかったので悩ましい)。
運営の皆さん、楽しい大会をありがとうございました。
追記
isuconのMVができたそうです(?) かっこいいですね。