isucon12 予選に出場し敗退しました

2022/7/23 (土) に開催された ISUCON12 の予選に参加し、最終スコア 9042 (3回の再現スコア計測で85%以下のスコアしかでなかったため失格) で予選敗退しました。

結果は予選敗退となりましたが、学ぶことの多い 1 日となったためブログとして書き残そうと思います。

問題概要

今回のお題は ISUPORTS というマルチテナント SaaS でした。 問題の技術的な概要は後日公式から講評ブログとして出ると思うので、ここには当日競技開始時に流れた動画のリンクを貼っておきます。

www.youtube.com

やったこと

10:00-10:30

今後の開発をスムーズに行うためのセットアップや、nginx, mysql に対する設定等をしました。

アプリケーションのデプロイには自作ツールの isucontinuous を利用しました。このツールを一言で説明すると、Git リポジトリソースコードをいい感じに複数台に展開するためのデプロイツールです。

上記作業は自分ひとりで済む作業だったので、チームメンバーの他二人はこの間にマニュアルを読んだりアプリケーションの振る舞いを理解する時間としました。

10:30-11:30

今回アプリケーションは Docker Compose で動作しておりマシン上に Go のランタイムはインストールされていませんでした。そのため Go のランタイムのインストールや systemd のユニットファイルの作成などを行いました。 また、alp のパス集約など解析のためのツールの調整も 11 時までに行うことが出来ました。

その後、MySQL のスロークエリログを pt-query-digest を通して見て明らかに重いクエリに対してインデックスを張るなどして、この時点でのスコアは5000点弱でした。

11:30 以降

今回の問題は DB が MySQL と SQLite3 とで複数存在する構成でした。 チームメンバーと話し合い、以下の点より「まずは MySQL にすべて寄せるべき」という方針をとりました。

  • 負荷の高いエンドポイントの実装が MySQL 側の DB で 1 クエリ打ったあとに SQLite3 側の DB で N 回クエリを叩くような N+1 だった
  • アプリケーションの負荷が大きかったため、このまま SQLite を残したままだと負荷がアプリケーションに寄ってスケールしないと判断した

メンバーのうち一人がその移行作業を担当し、それ以外のメンバー (自分を含む) は MySQL に移行した後に詰まるであろうポイントに目星をつけそこのチューニングを行いました。

しかしながらこの移行作業に想定以上に時間がかかり、移行作業をしていないメンバーも直近のボトルネックを無視した改善がなかなかベンチを通せず、かなりの時間を溶かしてしまいました。

17:00-18:00

17時頃になりメンバーが sqlite3 から MySQL への移行を完了してくれました。 しかし、ベンチマーカーの initialize リクエストで初期化すべき初期データが大きすぎて (特に player_score) 30秒だと微妙に間に合わない問題があり、17 時時点では事前に手動で initialize 処理を実行した後にベンチを実行することでスコアを確認していました。

17 時以降、「N+1 回クエリを発行している箇所を JOIN に修正」、「ユーザーのオンメモリ管理」、 「initialize を通す作業」をそれぞれ分担して行いましたが、git の merge で conflict が多発し時間も無かったため、この時間でやった改善のうち master に入った改善は結局追加でインデックスを張ることくらいでした。

結局 initialize が 30 秒以内に終わらない問題が解決できなかったため、最後の 10 分で 無理やり initialize リクエストを 30 秒以内で返すようなコード を書いて通したベンチが最終スコアとなりました。

振り返り

個人的に今回の一番の敗因はコミュニケーションミスだと思っています。

例えば 11:30 以降の戦略に関して、以下のような反省点があります。

  • MySQL への移行作業が後続のすべての作業の前提となっていたため、少しでも早く解決するために複数人で作業すべきだった
  • 想定よりも移行に時間がかかりそうなことが分かった時点で、他のメンバーは MySQL への移行が失敗したとしても点数を伸ばすためのフォールバックプランを検討すべきだった

当日上記のことが出来なかったのは、MySQL への移行作業を特に時間を切らず一人に任せきりにしてしまったことが大きな理由だと思っています。

また他にも 17 時以降の initialize が通らない問題に関して、他のメンバーは以下の解決先を思いついていたのですが、競技時間内のコミュニケーション不足が原因で作業者 (自分) はこれらを把握できていませんでした。

  • 案1. /var/lib/mysql 以下のファイルコピーを取って、initialize 時にディレクトリごとコピーする
  • 案2. player_score テーブルのレコードは WHERE で絞り込んだ後にrow_numが一番大きいものしか利用しないので、予め初期データから使われないデータを削除する

結局 17 時以外にインデックスを張る以外の改善が出来ていないため、今振り返ると 17 時時点で一度メンバー全員で足並みを揃えて残り 1 時間でやるべきことを落ち着いて話し合ってから作業に着手すべきであったと思っています。

最後に

このような素晴らしい問題を提供してくださった ISUCON12 運営の皆様方、ありがとうございました!

当日やったことを書き起こしてみると、単に歯が立たなかったというよりは「こうすればよかった」という反省の多い回となってしまったことがわかりとても悔しいです。 来年こそは悔いの残らない戦いをしたい!!