ISUCON8 本戦にて優勝してきました #isucon

こんにちは。優勝班のwhywaitaです。

去る2018/10/20に行われたISUCON8 本戦にチーム「最大の敵は時差」として出場し、優勝しました。
チームメンバーは全員学生で、学生1位との同時受賞によりISUCON史上最高の賞金額となりました!!!

今までのあらすじはこちらです。

リポジトリ

以下に公開しました。
(GitLabで開発していたのをコンバートしたので何かおかしいところはあるかもしれません)

タイムライン

最初の一時間

ほぼ環境構築でわちゃわちゃしていました。
予選同様Goでやるという事は事前に決めていたんですが、その後の事はあまり考えていなかったので当然入れる系の設定をとりあえず入れていたりしました。

アプリケーション担当の2人は自分のマシンで動かせるように動かしつつ、私がベンチマーク後の結果を計測するために各種ツールを入れたりしていました。

以前yuukiさんが書かれていたprebenchをほぼ踏襲していて、ミドルウェアの再起動やログ待避はまず間違いなく忘れないようになりました。
あとはプロファイラであるmatsuu/kataribemysqlslowdumpをインストールし、最初から最後まで大変お世話になりました。

初期実装スコア: 530

11:07

オーバーヘッドの存在を気にしたり、Go言語を選択した状態でのデプロイ方法を考慮してDockerを利用せずネイティブでインストールするようにしました。
Goの場合ワンバイナリで起動するため、手元で開発したコードをscpだけでデプロイ出来るのはかなり体験が良かったです。

今思うと、ネットワーク的にはそれほど詰まっていなかったためオーバーヘッドという点ではそれほど大きくスコアには関わっていなさそうです。

スコア: 646

12:58

予選でかなり良かったので与えられた4台のうち3台は1人1つ確保して自由に開発していたのですが、リソースを見る限りアプリケーションとMySQLがCPUを食い合っており、その後分割することも十分に考えられたのでさっさとDBは専用インスタンスを割り当てて外だししました。

あとは静的なファイルをアプリケーションではなく前段のNginxで返答するようにして、ISUCON7で苦しんだ Cache-Control の設定も追加しました。
リクエスト数を見ると完全に同じキャッシュを全てのユーザが使った訳ではないようだったのでそれほど大きいところではありませんでしたが…。

スコア: 759

13:11

この辺でアプリ担当の2人が環境構築が終わりコードも読み終わり、おかしい点を修正し始めました。

とりあえず分かりやすいところでSQLの修正を行い、スコアがグッと上がりました。

スコア: 4843

SQLで取得していた設定部分をアプリケーション側でキャッシュ

スコア: 5000ぐらい

14:54

同じbank_idに対して5回連続失敗したときに返して良い というマニュアルに従ってban_listを導入しています。
どういう基準でBAN扱いして良いのか分からず運営に質問を投げたりしていました。


スコア: 5000ぐらい (後で効いていた)

15:18

OHLC (Open High Low Close) の情報をアプリケーション側でキャッシュ

スコア: 5400

この辺りで enable_share という変数に気づき、trueにしました。
当然ユーザが増えすぎてベンチマーカがfailし続けていたのですが、今回のポータルサイトではfailしても参考スコアがベンチマーク結果のページに出力されていたため、それを参考に修正を加えていきました。

我々はこのenable_shareはtrueかfalseしかないと思っていたのですが、懇親会で聞いたところによると確率的に変更する事が可能だったようで、ユーザの増え方を調整することが出来たようです。
出題側の方と「学生はそこ気づくにくいポイントですよね!!」という話をしていて、確かにその通りだと感じました(本当に優勝出来て良かったですね……)。

15:58

ログ出力用のisuloggerに対して POST /send_bulk のエンドポイントを利用するようにしました。

isuloggerに関しては以下のような制約がありました。

  • 同時に発行できるリクエストは10並列まで
  • 1秒あたりのリクエスト数は20リクエストまで

ところがsend_bulkを最初実装した際にこの点を考慮して実装することを完全に忘れていて、isuloggerからエラーが返答された結果スコアが一気に下がったりもしていました。マニュアルはちゃんと読むとスコアが上がりますね!!

この修正から enable_share をtrueにした状態でもベンチマークを通る回が出てきたので、git上でもそれが反映されています。
(とはいえpass率はとても低く2割ぐらいでした)

スコア: 15000ぐらい

16:30

GET /info の高速化実装が終わりはじめます。
infoのデータは即時ではなく1秒以内に反映すれば問題無かったため、更新を専用のgoroutineにお任せするようにしていました。

この修正によりかなりスコアが跳ね上がりました。

スコア: 30000ぐらい

ついでに、ban_listなどにデッドロックが起こりえるコードが書かれていたので同様に修正されています。

16:41

ロウソク足の更新タイミングを変更することで、綺麗に高速化できました。

スコア: 32000ぐらい

残り1時間が見えてきた段階でサーバそのものの再起動実験を行ったりしていました。
この段階でもまだアプリケーションとMySQLのCPU使用率により詰まっていたのは分かっていて、分散すれば利用できるCPUリソースは増えるものの、一部実装などで分散すると危険そうだったのでこの辺で2台構成で最後まで行こうという話になっていました。

なお、この段階でもベンチマーカはfailし続けていて、大体3割ぐらいの確率でpassしていました。

17:03

アプリケーション担当の2名のうち1名は修正を加えつつ、私ともう1人でベンチマークの安定化を図っていました。
特にインフラ周りの設定をミドルウェア側もアプリケーション側も詰めていて、最終的にクライアントから送信するSQLの並列数をMySQLのデフォルトである128に揃えた結果、比較的安定し、8割ぐらいの確率でpassするようになりました。
(とはいえ2割は謎の原因で POST /orders が徐々に重くなりfailしていました)

スコア: 35000ぐらい

アプリケーション側のデフォルトとしてはほぼ無制限にSQLを並列化する挙動を行っていたと推測していますが、この制限を入れたことでアプリケーション側でキューイングが行われるようになったため、結果としてMySQLから返答する Too many connections が全くなくなり、結果としてスコアが安定したのではないかと考察しています。
ちなみに並列数をアプリケーション/MySQLの並列数を2500に増やした時はベンチマーカのfail率が上昇し、64まで減らした時はpassするもののスコアが10000点代まで下がっていたので、比較的悪くない数字だったのではないかなと思います。

結果として運営側で行った再試において1回でベンチマークが成功し、エラーも全く出力されずに終了しました。
レギュレーション的に1回目failした場合は運営側で再試験を行ってくれるという記載だったため多少不安定でもこのpass率なら…!と思っていましたが、かなり運に頼った戦い方になったと思います。

結果

最終的なグラフは以下のようになりました。
(運営向けポータルサイトで後から確認出来ました、ありがとうございます!)

実際には0点のベンチマークも多いのですが、反映されておらずうまくスコアが上昇しているように見えます。

まとめ

問題、会場ファシリティ、Discordの対応、などなど全ての点において非常に快適で、問題を解くことに専念出来ました。
ISUCON運営のみなさま本当にありがとうございました。
特に予選本戦どちらもベンチマーカが非常に快適で、トライアンドエラーと計測を細かくできた事が一番の勝因だと思います。
ポータルサイトのWebSocketで結果が流れてくる挙動など、ポータルサイトのクオリティが高く感動しました。

我々が優勝出来たのはある意味で運が絡んでいると感じていて、2位のtakedashiメンバーのこたまごさんのブログを読むと、我々が手を付けておらずかなりのボトルネックとなっていた RunTrade を解消されていたりとやられている事としては我々より多く、おそらくエラーによる減点が大きい今回の大会のレギュレーションで運良く勝てた点もあると思います。
この辺はインフラとして点を取るだけでなく点を守るというところで少しは貢献できたかなと思います。

というわけでまだまだ精進は足りなさそうです。これからも頑張っていきますのでどうぞよろしくお願いします。

追伸

ちなみに賞金の使い道ですがTwitterでも書いた通りバーンと使う予定です。
何か良い使い道があればぜひご連絡ください。検討します。

また、こちらもツイートした通りです。
もし興味があればご連絡ください。中の人に繋ぎます。

チームメンバーのブログが上がってきたので追記します


コメントを残す