ISUCON10決勝に参加しました

ISUCON10予選でなんと勝ち抜くことができたので、決勝に参加しました。
チーム名は「勉強不足の分は有り余る才能でカバーしようかなと思っております」で、チームメイトは@kenkooooさんと@GolDDranksさんでした。
今回もフルリモートでの参加になりました。
決勝に残ったチームとしては唯一のRustを使うチームになりました。

まだ、公式には点数が公表されていませんが、おそらく再起動試験で落ちて失格扱いだと思います。
再起動試験に通っていても競技中の最高スコアは11411点程度だったので入賞には程遠かったと思います。

今回は学生の一人チームが優勝したということでかなり驚いています。去年の予選は一人で参加して本当に何もできなく、
今回の決勝も対処すべき問題がいろいろと散りばめられているので、一人で全部なんとかしてしまうのは本当にすごいと思います。

当日やったこと

自分の視点からやったことを振り返ります。

8時半頃:起床。今回は前日にちゃんと睡眠がとれてコンディションはよいように感じた。

10時の競技開始前まで:朝食、朝のリングフィット、出前や飲み物などの準備

10時:今回は予定通りに競技がスタート。運営の皆様、お疲れ様です。

いつもどおりansibleスクリプトを走らせたり、マニュアルをチェックしたり、サーバーの構成を確認したり。
Ubuntu20.04になっていたことでうまく動かないところもあったが、なんとか修正してセットアップ。

今回はMySQLのバージョンも8にデフォルトでなっている他、サーバーはいつものnginxではなくenvoyになっているのでアクセスログのセットアップにかなり手間取ってしまった。
また、サーバーは3台与えられたが、スペックがそれぞれ異なり1台目は1GBのメモリに2コアのCPUだったが、2台目は2GBのメモリが、3台目は4コアにと増強されていた。
メモリが少なくてansibleスクリプトを走らせながらだとベンチマークが通らなかったりした。
とりあえず、メモリが多い2台目をメインのサーバーとして使うことにした。
Rust実装に切り替えてスコアは7000点程度という感じだった。

12時頃:アクセスログが取れるようになり、ボトルネック分析ができるようになる。
その間、@GolDDranksさんがダッシュボードAPIのキャッシュを導入したり、@kenkooooさんがチーム上限をいじったりしていた。
チーム上限をいじって異常に負荷をかけるとenvoyが途中で落ちてしまい、ベンチマークが動かないことがわかったので、下手にいじらないことにした。

12時半頃:SQLのログを見て適当にindexを張ってみる。今回は予選での反省を活かし、pt-query-digestを活用して改善が見込まれそうなクエリを探していった

13時15分頃:サーバー複数台の準備。コア数の多い3台目をアプリケーションに、2台目をSQLサーバーにという構成にしてみる。

実は初期実装に微妙なバグがあって、SQLサーバーのホスト名の参照すべき環境変数が間違っておりちょっと手間取る。
ベンチを回してみたところ、2台目をアプリケーションに、3台目をSQLサーバーにしたほうがよさそうだったので、とりあえずそういう構成に。
最終的にスコアが9928まで上がる。

14時50分頃:ダッシュボードAPIのキャッシュが導入されるもベンチは通らず。キャッシュの生存判定が緩すぎたっぽい

15時10分頃:Rust側でgzipを有効化。スレッド数の変更も試したが、かえってスコアが落ちるのでやめておく。
SQLのログを見ていると、コンテスト情報をとってくるためのSQLが頻繁に呼ばれているので、それをアプリケーション側でなんとかすることを思いついたので実装開始。

16時半頃:ダッシュボードAPIのキャッシュの生存判定をいじって1万を突破

16時50分頃:自分のコンテスト情報をアプリケーション側でキャッシュする実装が導入される、が、あんまりスコアが伸びない。
ダッシュボードAPIのキャッシュが効いて、そもそもこのSQLがそんなに呼ばれなくなったことが原因か

17時頃:3台目が余裕がありそうなのに対し、2台目がキツキツだったので、構成を再度逆にしようとする。
が、ベンチを回してみたところ、サーバーがフリーズ。メモリが枯渇したらしい。運営にサーバーを強制的に再起動してもらって、その後スワップメモリを導入しておく。

17時20分頃:@GolDDranksさんが一部のSQLのロックの除去を試みるも、ベンチ通らず。
サーバー構成変更も原因不明のエラーが出てしまってうまくいかなかったので、諦めて再起動試験やログを切るなどの最終調整に移行する。

17時50分頃:ベンチマークガチャを、と思い何回か回すが、直後に今回は追試でのスコアが最終スコアということを思い出し、無意味ということに気がつく。
とはいうものの、もうやれることもないので、撤退。最高スコアは11411点でした。

20時頃:結果発表。特に賞はもらえず。

後にコンテストサーバーに再度ログイン可能ということで調べたのだったが、なぜかブラウザからの動作確認ができないことに気がつく。
原因を追跡すると、自分の導入したコンテスト情報のキャッシュロジックが間違っていることに気がつく。
このキャッシュロジックは/initializeをするときにキャッシュに値を挿入するのだが、アプリケーションを再起動するとこのキャッシュの値は当然消える。
ベンチマークを実行している間はアプリケーションは再起動しないし、絶対/initializeが最初に呼ばれるのでエラーは起きないのだが、
今回の再起動試験は、サーバーを再起動させた直後ブラウザからの動作を確認する、というものが含まれていて、ブラウザから動作確認する場合は/initializeは当然呼ばれない。
そのため、サーバーがコンテスト情報をDBから取得できずにエラーを返してしまうため、おそらく失格になったと思われる。

感想・反省

再起動試験に落ちなかったとしても、1位が4万点台のスコアを叩き出していることを考えると、優勝までは程遠かった、ということがわかる。
サーバーは2台しか活用できなかったし、それもスペックをフルに活かせていない構成だったので、このへんの構成を変えただけでももっとスコアは伸びたと思う。
DBの詳細なチューニングも十分にする時間はなく、かなりやることが多いな、という印象でした。

envoyに振り回されたところもあるので、nginxなどある程度経験のあるものへの切り替えも視野に入れるべきだったかもしれません。が、切り替えられるほど習熟しているかどうかも怪しく、踏み切れませんでした。

戦略もあんまり正しくなかったかもしれません。もうちょっと俯瞰的にどこがボトルネックになっているかを手を動かす前に詳細に分析してあげることも必要だったと思います。
例えば、今回のアプリはAPIアプリとベンチマーカーの2つの構成となっていたのですが、このうち片方を別サーバーにしてあげるとかでも負荷分散ができたはずです。
どうやったらサーバー3台を活かすことができたのかがおそらく勝負の鍵だったと思うので、DBを分ける、程度しかできなかったのは敗因として大きかったと思います。

あとは、マニュアルをもっと読み込んで、再起動試験を真面目にやるべきでした。再起動後のベンチマーク試験のパスは確認していたのですが、ブラウザからの追試は完全に抜けていました。

優勝したチームは一人で全部やったらしいですが、そういう超人でない以上、チーム内での役割分担とかはもうちょっとなんとかできたかもしれません。
ただ、即席チームで決勝までこれたのはかなりよかったのかなあと思います。
来年も機会があれば、なんらかの形で参加したいです。

最後に、毎年このコンテストを開催してくれる運営の皆様、ありがとうございました。今回はRustという自分が好きな言語での実装が提供されてとても楽しかったです。