ISUCON11 予選お疲れ様でした! 「都営三田線東急目黒線直通急行日吉行」というチームで参加し、予選 2 位で通過することができました。 チーム編成は 5 年連続同じメンバーで、0gajun と izumin の 3 人チームでした。
ISUCON11 オンライン予選 予選結果と本選出場者決定のお知らせ
ISUCON7 以来の本戦出場で、またこのメンバーで本戦出場をきめられたこともうれしいですし、予選 2 位 / 100 万点越えを達成できたのも本当にうれしいです!
やったこと
言語は Go で、middleware まわりはとくに変なことはせず nginx + MariaDB のまま設定だけいじってすすめました。 例年は Redis やオンメモリで大胆なキャッシュを試みることが多かったのですが、今年は大人な戦い方で堅実にスコアを伸ばせたのでとても満足しています。
正確に記録をとれていない面もあるのですが、箇条書きでやったことを列挙してみます。 作業 repository も公開する予定です。
- 初期スコア
- 1803
- 10:38 いつものように実装だけみて足りない index をはる
- 17124
- 大体いつも、書き込みがボトルネックになったら見直す、という前提で雑につっこむ
- 11:10 postIsuCondition の INSERT をまとめる
- ほぼ変わらず
- 11:20 getTrend の N+1 をつぶす
- 18614
- 11:40 無駄な
SELECT *
を消す- 19240
- 11:58 keepalive と worker connection 追加
- 20028
- 12:33 静的ファイルを nginx から配信する
- 19300
- 13:07 各いすの最新コンディションを別テーブルに保存
- 19936
- 13:12 index.html に cache-control
- 25392
- 13:19 dropProbability を 0.9 → 0.7 に
- 13084
- 捌ききれなかったが、↓ の複数台構成とセットで伸びたのでヨシとした
- 13:28 DB を別サーバーに出して 2 台構成に
- 31258
- 13:33 echo の余計なアクセスログを消した
- 37395
- 14:00 リクエスト drop 時のログを消した
- 40120
- このあたりまではアプリケーションの CPU が限界だったので、ログをきるだけで大きく伸びた
- 14:11 condition_level を事前計算し、それをつかって filter する
- 83782
- ??:?? このあたりで dropProbability を 0.7 → 0.4 に
- 94000 くらい
- 14:45 グラフ生成時の SELECT を適当に 25 時間分にしぼる
- 114958
- 15:03 25 時間分にしぼったつもりが、start 側しかしぼれていなかったので修正
- 143372
- 15:11
/api/trend
にほぼ同時に来たリクエストを束ねて計算を省略(singleflight)- 161966
- 50ms スリープし、その間にきたリクエストに対して 1 回だけ trend を計算するようにした。
- 16:06 DB1 台、App2 台構成にした
- 198583
- 16:16 書き込みが限界だったので、DB を 2 台にして最新コンディションのテーブルへの書きこみを分離 (DB, DB + APP, APP 構成)
- 214492
- ??:?? dropProbability や Load Balancing の調整、middleware の設定をなんやかんやした
- 30 万くらい
- ここからひたすら書き込み負荷を減らす努力を開始
- 18:12 postIsuCondition の書き込みを非同期 & Batch 化
- 372101
- fail にびびって 10ms 以内の書き込みしかまとめないようにしていたが、もっと攻めたら書き込み負荷をより軽減できたかもしれない
- 18:15 isu_condition の timestamp sort をアプリでやることで index を消す
- 400792
- index をひとつ消すだけで 30,000 も伸びた。DB が完全に限界にきていて、これを緩和すればするほどスコアが伸びる状態。
- 18:19 slow query log などを disable
- 593482
- !?
- 18:30 interpolateParams=true
- 1045520
- !??!??
すすめかた
インフラ的なパラメータ調整などは完全に 0gajun にまかせていたのですが、下回りでつまることが一度もなかったので感動しました。最終的につかわなかったのですが Redis の用意までしてもらったり、デプロイやプロファイルまわりの整備もやってもらい、アプリの改善が 120%活きる状態をつくってくれました。 例年は izumin の実装速度にものをいわせた飛び道具を仕込む戦いをしていたのですが、今回は大人にたたかおうということで堅実なアプローチを採用するよう心掛けていました。そういう堅実な手を数多くためすことができたのは、やはり izumin の手によるところが大きいです。Go への慣れという面でも頼りになりました。 このチームでまた本戦に出場できてよかったです。
手をつける場所の選定やコストのかけかたが今回はかなりうまくいった気がします。 グラフのところなんかは誰も最後まで仕様を正確には理解していなかったのですが、「ぱっと見いけそうだから雑に 25h でしぼってみる、だめだったらちゃんと理解してからやりかたを考えよう」で突撃したのがうまくはまったりしていました。25h なのは、仕様を理解していないので保守的にとってみたことの表れです。
マニュアルに書き込みを遅延してもよい、というような内容が記載されていたので、そこがキモかな、とおもっていたのですがどうだったんでしょう? 僕たちのチームは最終的には書き込みの負荷をいかに減らすか、というところが壁でした。 正直終盤の伸びは、自分でもなんでこんなに伸びたのかよくわかっていなかったりしますが、割とぎりぎりまでチューニングされていたからちょっと負荷がへるだけで大幅な伸びにつながった、と解釈することにしました。
まとめ
最近の ISUCON ではいつもアグレッシブな最適化をした結果、謎 fail が発生するようになる、というパターンで苦しんでいたのですが、今回は fail に苦しまず最後まで改善をつづけることができました。 堅実に改善していったものがハマって最後に爆上げする、というパターンははじめて経験したのですが、最高に楽しいですね!
運営のみなさま、ありがとうございました!問題もわかりやすく、ボリュームもちょうどよくて、問いていてとても気持ちよかったです! 本戦もがんばります!
おまけ
go1.17 から(フラグで有効にすれば部分的に) Generics がつかえるようになったので、実は前日まで Generics つかいたおしてみようか、という話をしていました。
意外とやりたいシーンがなく、 結局お蔵になったのですが、izumin が Go Generics で snippet を用意してくれていたので、せっかくだし載せておきます。
(「やりたいシーン 4 回くらいあったぞ!!!!! by izumin」 とのことです。)