なまえは まだ ない

思いついたことをアウトプットします

ISUCON14に出場して惜しくも優勝を逃しました(全体126位)

この記事はフラー株式会社 Advent Calendar 2024 10日目の記事です。9日目は@akiyama1020 の「「品質」とは?」でした。

ISUCON出場したよ

さて、今年も会社のメンバーとチームを組んでISUCONに参加してきました。開催は今年で14回目、私は9回からほぼ毎年出ていて、今年で5回目になります。 社内でチーム分けの結果、私は柏の葉オフィスのメンバーと「KOIL507」というチーム名で参加しました。ちなみにKOILは弊社がオフィスを構えるビル名、507はテナントの部屋番号です。

isucon.net

結論から書くと、最終スコアは10,381(ベスト:11,079)で全体126位という結果に終わりました。本当にあと一歩というところでした。いやぁー惜しかった!

ブログを書くまでがISUCONなので、今年も例によって参加記を残しておきます。当日までの準備、当日の動き、反省点や学びなどをまとめていこうと思います。

準備編

pprotein を導入する

毎年ISUCONの時期が来たら計測用のツール等を準備する秘伝のタレをメンテナンスしたりしているのですが、今年は以前からISUCON出場者の間で話題となっていた pprotein を導入することにしました。

github.com

pprotein は12, 13回優勝チームであるNaruseJunのメンバーが開発・公開してくれているツールです。今年は作問側に回られていましたね。本当にお疲れさまでした。 毎年ベンチマーカーを回した後のアクセスログの分析や結果の共有、再度ベンチマーカーを回す前の手順の多さ(アクセスログをフラッシュしてNginxを再起動したり、、)にうんざりしていたので、その辺りの効率化を狙って利用させてもらいました。

なお、pprotein に入門する際はこちらの記事が大変参考になりました。ありがとうございました。

zenn.dev

pprotein 用のサーバーを育てておく

pprotein で情報を管理するためには、pprotein が稼働しているサーバーから競技用サーバー側のエージェントに対してリクエストを送信する必要があります。単純に考えたら競技用サーバーが立ち上がった後、同一VPC内にもう一台サーバーを立て、そのサーバーから通信を飛ばせば良いです。が、チームで話し合ったところ、次のような懸念が出てきました。

  • 競技が開始してからpprotein用サーバーをセットアップするのは、どう頑張ってもそれなりの時間を要する
  • 中途半端に競技用環境をいじって環境チェックでFailしたくない

色々考えた結果、pproteinサーバーは競技用のVPCとは独立した場所にあらかじめ構築しておき、各サーバーとSSHポートフォワーディングをすることで接続を確立することにしました。これなら競技用環境には影響を与えませんし、何よりあらかじめpprotein用サーバーをセットアップした状態で競技を始められます。

当日の段取りを決めてリハーサルしておく

当日は初回ベンチを回してアプリケーションの分析ができるようになるまでをどれだけスムーズにできるかが勝負だと考え、競技開始から初回ベンチを回すまでのステップをコマンドレベルで書き起こした手順書を作りました。SSH接続設定などの雛形も作っておき、なるべく何も考えずとも初期設定を終えられるようなものを目指しました。

手順書を作ったら本番直前にチームで読み合わせしつつ、練習用環境を作って流れを確認しました。実際に動かして検証できたことが当日すごく活かされたと思います。

当日やったこと

競技開始後の初期セットアップ

先に書いた手順書を使って初回ベンチまでの流れをザザッと済ませました。具体的な手順を書けたことと事前に検証できたことが大きかったです。 やったことをざっくり書くと、次のような感じです

  1. CloudFormation テンプレートのデプロイ
  2. IPアドレスが判明したらSSH設定のテンプレを更新し、自身のPCとpproteinサーバーに設定
  3. 各サーバーでSSH用のキーペアを発行してGitHubに登録。リポジトリをPull&Pushできるようにする
  4. pproteinサーバーから各競技用サーバーにSSHポートフォワーディングを確立
  5. 各競技用サーバーでセットアップ用レシピを流し、アクセスログの設定やpprotein-agentのインストール
  6. アプリケーションをクローンして、pprotein用の設定を追加
  7. サーバー側でビルドし直して、ベンチを回す

途中の手順は次のようにsshコマンド経由で実行したので、わざわざ各サーバーに入って実行してまた出て、、という煩わしいことをしなくて済みました。

ssh isucon1 'ssh-keygen -t ed25519 -N "" && cat /home/isucon/.ssh/id_ed25519.pub'

slp の分析結果からインデックスのあたりをつける

他メンバーにアクセスログやアプリケーション、マニュアルを眺めてもらいつつ、自分はpproteinで収集したslpの分析結果を眺めてました。 競技中に使ってみて改めて実感したけど、pprotein便利すぎです!もうなかった頃には戻れない!!

具体的には、slpの結果を Rows_Examinedの平均値で降順にソートし、Rows_Sentの値とのギャップが激しいクエリからインデックスを決めていきました。 ISUCONあるあるというか、基本的にテーブルにインデックスなんか貼られてないので大体何やっても早くなりそうですが、実際のクエリから推測できたので効果的なインデックスを選定できたような気がします。

これによってスコアが3,000ちょいぐらいまで伸びました。

マッチングのアルゴリズムを変更する

色々小手先の修正をするがどれもあまり効果が出ず、例年の問題のように「N+1をJOINで解決!」みたいなわかりやすい修正箇所も見つからず、ちょっと焦りが出ました。 何か打開策を見つけようとマニュアルをよく読んだところ、どうやらライドと椅子のマッチングを高速化するとスコアが伸びるようになってるらしい、ということが見えてきました。 またベンチマーカーのログからも、多くのユーザーが椅子のマッチまでの時間に不満を抱えており、これの改善が何か糸口のように思えました。

ここの実装の読み込みや修正にかなりの時間を食ってしまいましたが、最終的に次のようなロジックにしました。

  1. 椅子のIDをキーに、その椅子が現在空いている(=マッチングできる)かを調査できるテーブルを追加
  2. 椅子の最新の位置情報のみを管理するテーブルを追加
  3. ライドの情報を引数で渡すと、その時点で空いている椅子を全取得して最も近くにいる椅子とマッチングさせる関数を実装
  4. ↑の関数を「ライドを登録したリクエスト」の最後に非同期で呼び出すように修正

リファレンス実装ではマッチング処理を別プロセスで定期実行させてましたが、これライドが登録した瞬間にマッチングすれば良いんじゃね?と閃いてこのような実装にしました。 この辺の実装が落ち着いたことで、スコアが8,000くらいに到達するようになりました。

状態管理ミスによるバグ、ベンチFail地獄

競技終了まで残り1時間を切ったところで、MySQLサーバーを別サーバーに切り出すことを計画しました。

しかし、不思議なことにMySQLサーバーを別サーバーとして切り出すとInitializeエンドポイントがコケるようになりました。 ./webapp/sql/init.sh を実行したらちゃんと別サーバーにコマンドが飛ぶので、環境変数設定はミスってない。スクリプトもちゃんと動いてるから特にミスってない。でもAPI経由だとコケる。なんで???ってなりました。 最終的にこれは私が入れた初期化処理がバグっていたせい(そもそも不要だった)で、その処理を抜いたら上手くいったのですが、これでまた無駄に時間を食ってしまいました。他にも椅子の状態管理をミスっていたことで稀にベンチがコケるバグもあったりと、残り30分もない中でとてもバタバタしました。

最終的にこれらのバグ修正を終えて無事MySQLサーバーを別サーバーに逃がすことに成功し、最後にアクセスログやスロークエリログ、Binログの設定をOFFにしてベストスコア(11,079点)を出すことができました。

スコアの推移

まとめ

反省点

例年と比べ、よりアプリケーションのマニュアル、アプリケーションの仕様やコンセプトをきちんと捉えないといけない問題のように思えました。難しかったですが、とっても楽しくて良問だったと思います。

戦う中で出てきた反省点は次のとおりです

  • もっとマニュアルを読んでアプリケーションそのものの仕様や特性を理解する。ただし途中でマッチングの重要性に気付けたのは良かった
  • MySQLサーバーを別サーバーにする判断がさすがに遅すぎた。構成をいじる判断は大体の目安時間(16時になったら〜とか)を決めておくべきだった
  • pprotein を入れるまではスムーズだったが、もっと使い倒したかった。実際に過去問を解きながら使って感触を確かめるべきだった

これらの反省を来年に活かし、来年こそは上位入賞狙いたいです!!

感想

とても楽しい問題でしたが、いくつか目に見える改善をできただけに「もっと戦えただろ!」という悔しさが残る結果でした。

途中で詰めきれずに諦めた改善点を1〜2つきちんを改修しきれたら、もっとスコアを伸ばして上位入賞狙えたと思います。本当に惜しかったです。 全然根拠も自信もないけどそんな気がしてなりません。

というわけで、ISUCONに出場した皆さま、運営に携わった皆さま、本当にお疲れさまでした!

余談①

今年のチームIDが101番だったのですが、101は弊社での私の社員番号で、何か運命的なものを感じました。 ちなみにポケモンだとマルマインになります。

余談②

大会当日のホテルを会社が用意してくれたのですが、部屋番号がチーム名にもなっている会社の部屋番号と同じで笑いました。


明日は@masaya82で「UIコンポーネントをライブラリに頼ることについて書く予定」です。お楽しみに!