ISUCON14参加記(124位)

ISUCON14にチーム blue最高!として参加して13188点で124位だった。

メンバーと役割分担

今年の目標

今年のチームの(というか自分の)目標は、「複数台のサーバーを活用する」だった。

この目標にした理由は、去年は1台しかサーバーを使っていなかったため。

使ったツールなど

  • GoLand
    • 主な開発環境
  • GitHub Copilot
  • Visual Studio Code
    • 初期設定時に、リモートファイルを編集するために使用
  • ansible
    • 初期設定
    • 変更のデプロイ
  • pprotein
    • 測定
    • 内部でalpslpを使用
      • alpは毎年お世話になっているツールなので、tkuchikiさんをスポンサーした。いつもありがとうございます。
  • pt-query-digest
    • スロークエリーログの初期解析
  • netdata
    • モニタリング

練習

個人練習

11月上旬から練習を始めた。1人で行った練習は次の通り。

  • ISUCON13
  • ISUCON12予選(3回)
    • MySQLへの移行2回、SQLiteのまま1回
    • MySQLへの移行2回目は、MySQLのデータベースをバックアップから復元する手順を確認するために行った
  • ISUCON12本選
  • ISUCON11予選
  • ISUCON11本選
  • ISUCON10予選

各問題で、

  1. 初めは自分でできるところまでやり、
  2. 行き詰まったら解説を見ながら書いてあることをやり、
  3. 最後に上位陣のブログやコードを読んでできるところまでやる。

という感じで練習した。チーム練習後は、初期設定の手順表を書き、それに従ってセットアップを行うことを意識して練習をした。

11月中は、平日は仕事が終わってから、休日は別の予定がないときに、ほぼ毎日練習をしていた。今までで一番真剣にISUCONの練習をしていた。

目標は複数台の活用だったので、今年はAWSインスタンスを立てて練習した。約1ヶ月でかかった料金は28.47ドル(4,414円)だった。後で11月中にクーポンを適用しておけば良かったと思った。

プライベートでも、仕事でも頑張っていて、燃え尽きてしまったのか、12月に入ってからはあまり練習しませんでした。主にスロウスタートやお兄ちゃんはおしまい!を読んで過ごしていた。

チーム練習

チームでの練習は11月24日に一回行った。使用した問題はISUCON12本選だった。

練習前はVS Codeリモート接続 + Liveshareでみんなで一つのサーバーにアクセスして作業しようと話していた。しかし、練習中にLiveshareが不安定であるなど都合が悪いことがわかったので、各自でリポジトリをローカルにダウンロードして開発することにした。

本番

得点の推移

開始後〜初期設定

この間に他のメンバーがやっていたこと:マニュアル読み、アプリケーションの確認

サーバーを起動するときに、練習用に使っていたAWSアカウントにCloudFormationの権限が付加されていないことに気がつきプチパニックになる。ほぼ全ての権限を持っている別のアカウントがあったことを思い出したため、そちらでサーバーを設定する。

初期設定用のplaybookを実行し始める。playbookは実行が終わったら、ベンチを実行できるように作っていた。(alp用のNginxのアクセスログMySQLのスロークエリーログの設定など) alpやnetdata、pprotein-agentのダウンロードがうまく動かずにプチパニックになる(エラー内容は保存していないが、たぶん証明書関係のエラーだったと思う)。とりあえず、該当箇所をコメントアウトしてなんとか終了させる。

追記:どうやらansibleのバージョンが古かったことが原因らしい。

終了後ベンチマークを走らせる。なぜかアクセスログやスロークエリーログの設定がうまくいっていなかった。適当にガチャガチャやっていたら治ったので、再度ベンチを動かす。 alp用のパスへの正規表現を書き、alpの結果と、pt-query-digestの結果をチームメイトに共有する。(この時点で10時30分ぐらい)

アプリケーション側のpproteinの設定をしようとしたが、フレームワークがEchoじゃなくて混乱する。pproteinのインテグレーションコードを見たが、chiのインテグレーションがなかったので悩む。 アプリケーションのpproteinをあきらめ、chiでpprofをホスト+pprotein-agentでNginxとMySQLのログをホストにするようにする。 pproteinの設定がうまくいく。(この時点で10時45分ぐらい)

設定ファイル(nginx.conf、mysqld.conf、env.shなど)をリポジトリに追加する。メンバーにmainブランチの準備ができたことを共有。795点(10時50分ぐらい)

DBサーバーの切り離し

この間に他のメンバーがやっていたこと:

  • baw: ride_statuses, chairs, chair_locationsにインデックスを張る
  • saikou: JSONのライブラリをgo-jsonにする

DBサーバーをアプリケーションのサーバー(S1)とは別のサーバー(S2)で動かしにかかる。

11時15分ごろにDBをS2に移し替える設定が終わったのでベンチマークを実行するが、失敗する。

なんで失敗しているのかわからずに色々試している間にbawさんのインデックスのコードができたので適用してベンチを走らせる。2250点。

DBの初期化をHTTPエンドポイント経由で試したり、S2のアプリケーションを停止したりして試したが、ベンチ成功せず。 マニュアルを読んでみるとisuride-matcher.serviceなるものの記述を見つける。これじゃないかと思い、S2でサービスを止めてベンチを走らせると成功。5353点。(12時10分)

isuride-matcher.serviceをS2と3台目のサーバー(S3)でdisableしておく。

chair_locationsのテーブル圧縮(失敗)

この間に他のメンバーがやっていたこと:

  • baw: rides, couponsへのインデックス追加(7814点)
  • saikou: ユーザー情報のキャッシュ

chair_locationsが位置の履歴を全部持っているので、chair_locationsに最新位置だけを持ち、chair_locations_historyというテーブルに今までの位置の履歴を保存するようにする。

が、追加したSQLファイルをinit.shに追加するのを忘れ、スコアに影響が出なかった。「あれえ?」と思いながらも、とりあえずマージする。7668点(1時ごろ)

これのちょっと前(12時40分ごろ)に、一瞬12位になっていたらしく、チームメイトが盛り上がっていた。

ride_statusesをS3に移す

この間に他のメンバーがやっていたこと:

  • baw: isuride-macherのインターバル調整(11328点。2時ごろ)
  • saikou: オーナー情報のキャッシュ

3台目の活用方法を考える。NetdataをみるとDBにかなり負荷がかかっているので、その時点でスロークエリーのトップに来ていたride_statusesをS3に移動することにする。

10329点。bawさんが、chair_locationsの圧縮のSQLファイルをinit.shに追加していないことに気がついて、修正してくれた。その変更も合わせて13380点。2時50分ごろ。

DBサーバーを分離した後で、Netdataを眺めるとS3よりもS2の方がMySQLのCPU利用率が高かった。変更量が多かった割に、よくない変更だったかもなと思った。

chairStatsのN+1を修正&chairPostCoordinateのSELECT削減&イスごとの最新ride情報のキャッシュ

chairStatsのN+1を修正する。14431点。3時40分ごろ

chairPostCoordinateで挿入した行をSELECTで取得しているのをやめる。15833点。4時ごろ。これがチームの最高点になる。

イスごとの最新ride情報をキャッシュする。13586点。4時50分ごろ

キリもいいので、ログをオフにして再起動試験を開始する。

ログをオフ&再起動試験

ログをオフ。12574点。5時10分ごろ

チームのみんなでbawさんの画面を見ながら再起動試験を行う。再起動前と再起動後でイスの状態が違うことがあった。イスごとのride情報のキャッシュが悪さをしているのではないかという話になり、その変更をRevertする。

再起動試験はかなりギリギリまで行った。

得点公開後

追試で0点になったように見えたので、原因箇所の特定をしようとした。しかし、原因として考えられるデータを探してみても、該当データが見つからず、はっきりとした原因はわからないままだった。

反省

  • 初期設定の目標は30分だったが、1時間かかってしまった。とはいえ、想定外の事態(AWSの権限、ツールのダウンロードに失敗、Webフレームワークが変更)が複数あったので、よくやった方ではないかと思う。
    • ソースを書き始めるのは、設定ファイルをmainブランチに追加するまで待ってもらっていたが、初期設定終了後にコードをダウンロードしてもらっても良かったのではないかと思う。
  • 変更をデプロイできるのが自分だけだったので、コードの変更中にデプロイ依頼が来て集中力が途切れることが多かった。次回は各自でデプロイできるようにしたい。
  • 自分もマニュアルを読んだ方が、改善点が浮かびやすかったのかなと思った。
    • 初期設定で時間を取られるので、すぐにコードに手をつけたくなるが、じっくりマニュアルを読んだ方が改善点や制約を理解できたのだろうか。
    • 他のチームメイトにマニュアルの重要なところまとめみたいなものを作成して欲しかったなと思った。
  • 序盤の動きは良かったが、後半伸び悩んだ。
    • ちゃんとボトルネックを解決できていなかったのではないだろうか
  • ride_statusesのサーバー切り離しは余計だったかもしれない
    • サーバーを3台ちゃんと活用したいという気持ちと、時間制限から早く方針を決断したいという気持ちから、急いで着手したが、焦りすぎたかもしれない。
  • チームメイトが効果のなさそうな作業をしている気配を感じたらちゃんと止めるべきだった。

感想

去年よりはちゃんとコードを書いて得点を伸ばすことができたのでよかった。来年は特別賞ボーダーを越えられるように頑張りたい。

大会を運営している人たちには感謝をしている。しかし、追試の存在には強い疑問を覚える。

コンテスト終了後に追試で0点になる可能性があるのはかなりの恐怖である。コンテスト終了後に追試で0点になってしまったら、その日の努力が全部水の泡になる。

参加チーム数を1000に増やしたことから、おそらく追試も自動化されているのだろうと察する。もしそうなのであれば、追試を行う機能をコンテスト中に実行できるようにしてほしい。コンテスト終了後に、急に0点だと言われるのはかなり辛いだろうから。

また、(おそらく)ベンチが安定しておらず、最高点から3000点近く下がってしまうのは競技としてどうなのかと思った。