【ISUCON】"準備が命"と身にしみて感じたISUCON14参加体験記

# はじめに

こんにちは、kenです。好きな言葉は「よいお年を」です。

先日、ISUCON14に同期の2人(@kota33 / @raorao1011)と参加してきました。
同じメンバーで去年も出場しており、そのときは悔しい結果に終わったので今回はリベンジ戦として挑戦しました。

最終結果は**スコアが4701点で順位が282位**。
全参加チームが約800チームということなので「低くもないけど高くもない」という微妙な結果ですが、それでも前回の成績を上回ることはできていたので成長を感じられる良い機会となりました。


![貼り付けた画像_2024_12_14_11_50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/2186aca1-c601-8ec9-9b2e-63e7eabaa357.png)

この記事では、当日までの準備や本番当日の動きをタイムラインに沿って振り返り、最後に反省や感想を書いていきます。ISUCONに興味がある方や、来年参加してみようかなと思っている方の参考になれば幸いです！

本記事はHRBrain Advent Calendar 2024の15日目の記事です。

https://qiita.com/advent-calendar/2024/hrbrain



# 参加体験記

## 時は戻って去年のISUCON13
先ほども書いたように、今年のISUCONに挑んだチームで去年も「ISUCON13」に参加していました。その時の結果は661組中297位で、半分より少し上という順位でした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/ab40e98d-d6a6-d13e-b65b-2826b4dd1843.png)


初出場でのこの結果だったので健闘したなという気持ちもありつつ、私個人としては正直悔いが残る大会でもありました。
というのも、当時は環境のセットアップや計測の手順に手間取ってしまい、最終的に改善できたのはインデックスを少し貼る程度の軽微な対策だけ。また環境構築もチームメンバーに全て任せきりで、私は事前準備不足のせいでほぼ何もできず、ただ見ているだけという状態でした。

この経験が非常に悔しく、当時は申し訳ない気持ちでいっぱいになりました。
次のISUCONこそはチームに貢献しリベンジを果たさねば。そういった気持ちを強く抱きました。


## 2024年7月（ISUCONの5ヶ月前）

今年の夏頃、少し時間があったので「今のうちにISUCON対策をしておこう」と思い立ち、準備を始めました。
去年の経験から「パフォーマンスチューニング以前に、環境のセットアップを素早く行えないと勝負にならない」ということを痛感していたので、まずは環境構築について情報を集めることにしました。

具体的にはネット検索でISUCONに関する記事を読み漁り、上位チームのリポジトリや対策記事をチェックしてZennのScrapにまとめていきました。
まとめる過程で様々なチームの「秘伝のタレ[^1]」を見ていくのは非常に楽しかったです。公開してくださっている方々には頭が上がりません。
[^1]: ベンチマーク実行前に実行するスクリプトや各種ツールのインストールコマンドをまとめたスクリプト集のことを秘伝のタレと呼んだりします。

https://zenn.dev/ken_hashimoto/scraps/2413db7b459fad

ここで集めた情報をもとにGitHubのリポジトリを作り、そこに自分なりの秘伝のタレを作っていきました。
他の方のコードの寄せ集めのようなリポジトリで、自分オリジナルの要素が少ないため外部に公開はしていないのですが、そのリポジトリには当日の動き方とMakefileを入れています。
（当日の動きについて書いた`setup.md`の一部↓）
![スクリーンショット 2024-12-14 12.38.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/adf151cb-dc80-df78-0d83-49bb9e8d47f5.png)


## ISUCON1週間前〜前日

その後は忙しい時期が続き、あれよあれよとしている間にISUCON14がもう目前に迫っていました。
直前期も思うように時間がとれず、結局チーム全員で集まっての練習や作戦会議は事前に行えませんでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/8ae3b8f7-d12e-a6e7-465d-5723ad71957c.png)

## 前日
ただ、このままでは去年の二の舞いになってしまいそうだったので前日の夜に環境セットアップの部分の対策だけすることにしました。

具体的には、[ISUNARABE](https://isunarabe.org/)という非公式のISUCON練習プラットフォームを使って準備を進めました。CloudFormationを使って本番さながらの環境を構築し、夏に用意した「秘伝のタレ」でセットアップが完了するかを実験しました。
うまくいくと思っていたはずが、いざ試してみるとうまくいかない箇所が多々あり、結果としてMakefileを大幅に修正することになりました。仮にこの準備をしていなかったら当日慌てていたと思うので、少しの時間でも準備をしておいてよかったなと感じました。

（↓当日大急ぎで秘伝のタレを修正する様子）
![スクリーンショット 2024-12-14 12.41.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/5658ad5a-1ab0-c1e8-77a8-9c35e276086f.png)

また、スロークエリのログや`alp`を使ったnginxのアクセスログの集計結果を、チーム全体で共有するためのDiscordサーバーを作ったりもしました。[他のチーム](https://github.com/oribe1115/isucon12-final/blob/main/Makefile#L50-L56)を参考に取り入れたものですが、各々がこのDiscordサーバーに投稿されたログを見て改善を回せるようになったので取り入れてよかったなと思います。

![スクリーンショット 2024-12-14 12.51.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/218c9396-8760-69e6-8262-fa75aa3ba6d2.png)

## ISUCON当日

いよいよ、ISUCON当日がやってきました

### 9:30 ~ 10:00
当日は9時半に会社のオフィスに集合しました。私が到着すると @kota33 はすでに到着していました。一方、@raorao1011 からは「少し遅れる」との連絡があり、まずは2人でスタートすることになりました。

ISUCON開始前に初動の方針を軽く打ち合わせ。以下の役割分担で進めることにしました：

* 私：各種計測用ツールのインストールとGitリポジトリの準備
* @kota33：MySQLとnginxのログ出力設定

また競技用のリポジトリをprivateで作成し、チームメンバーを招待したりなどの準備もしました。

ドキドキとワクワクが混ざり合う感情の中、10:00になりISUCONがスタートしました。
### 10:00〜10:30 (668点)
競技が開始して、[アプリケーションマニュアル](https://gist.github.com/wtks/8eadf471daf7cb59942de02273ce7884)を確認すると、今回の題材はライドチェアサービスとのことでした。

ざっとマニュアルを流し読みしようとしましたが、腰を据えて読み込む必要がありそうだったので、打ち合わせ通り初動の環境セットアップに専念しました。
前日の準備が功を奏し、各種計測ツールのインストールやインスタンスのGit管理はスムーズに完了。10時半には最低限のセットアップが整いました。
またその間に @kota33 はログ設定の変更を進めるとともに、AIツール [Devin](https://devin.ai/)の導入まで行ってくれました。Devinはリポジトリを解析して自動でPRを作成する機能を持っているとのことだったので、期待が高まりました。

初期セットアップのあと、何も改善を入れてない状態でベンチマークを回すとスコアは668点でした。
### 10:30 ~ 12:00 (668点)
最低限のセットアップを終えたあとは、より詳細なメトリクスを見るための準備をしていきました。具体的には以下の対応を行いました。
* `pprof`の導入
* `nginx`のログフォーマットの修正
* `netdata`の起動
* `alp`でログ集計を行う際のパスのグループ化



最後のalpの設定はリハーサルでやっていなかったので勝手がわからず少し手こずりました...。やはり事前準備は大事ですね。
一方で`netdata`は去年 @kota33 が使っていたのを見て導入したのですが、起動までが簡単でかつ`journalctl`のログや`top`コマンドの結果が簡単に確認できて便利でした。
![スクリーンショット_2024-12-08_21_41_36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/42974498-64d3-c4ba-1399-b6e8488c734f.png)
その間に @kota33 はスロークエリログの結果をもとに、インデックスを貼ったりSQLを改善してくれたりしました。
また11:30頃にAIのDevinがPRを出してきたので、そのレビュー等も行ってくれました。
ただ、このDevinの出したPRを入れると初期データチェックに失敗するようになってしまったのでしばらくは点数が0点のままになりました。

![スクリーンショット 2024-12-14 22.20.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/1f838100-5960-e991-2054-f0cb334b8241.png)

結局このAIの書いたコードは最終的にRevertすることになりました:cry: 
そんなこんなをしているうちに、遅れていた @raorao1011 がオフィスに到着しチーム全員が揃いました。

### 12:00 ~ 13:30 (2592点)
到着した @raorao1011 が、早速Discordに通知されていたスロークエリと`alp`の結果を元に複数のインデックスを追加してくれました。
この改善で一気にスコアは**2592点**に跳ね上がり、チーム全員に「ISUCONしてる感」が漂いました。

![貼り付けた画像_2024_12_14_14_25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/33669f63-bd8b-5dcf-ed4d-56e914cb1bf5.png)

私はその間、ベンチマークごとのログローテーションがうまくできていないことに気づいたのでその改善をしていました。
また@kota33 は、`sqlx`で実装されている初期実装を型安全な [`sqlc`](https://github.com/sqlc-dev/sqlc)に置き換えられないかという検証を進めてくれました。これまでの作業では、クエリを書き換える際のミスが原因でビルドに失敗することが多々あり、その要因として`sqlx`が型安全でないこと、そして使い慣れていないことが挙げられました。もし `sqlc`に簡単に移行できれば、この問題を解消できるのではないかという期待がありました。

ただ、ISUCONのコードには複雑なクエリも多く、全てを`sqlc`に置き換えるのは現実的ではないと判断し、最終的には`sqlx`を使い続けることになりました。
### 13:30〜14:30
ようやく軌道に乗ってきたものの、流石にお腹が空いてきたのでチーム全員でお昼をとることにしました。
オフィス近くの飲食店に行き、私は豚キムチ丼を食べました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/86a28b1e-3199-9b42-a8d8-b7544515af6d.png)

ランチを食べつつ、最近流行りのブラウザであるArcの話をして盛り上がりました。
このランチは、熱くなっていた頭を冷やす良いリフレッシュになりました。

### 14:30〜17:00 (4677点)
ランチから戻ってお昼の14時半。ISUCON終了まであと3時間半となっていました。
ランチ中に簡単に試せる[クエリの改善](https://github.com/ken-hashimoto/isucon14/commit/69f5cd49e9810185e31188c87123b2d41dfe5dee)をひとつ思いついたので、オフィスに戻るとすぐにその実装に取り掛かりました。
その実装を取り入れてベンチマークを回すと改善が効いたようでスコアは3604点まで上がりました。
インデックスを貼る以外の改善ができたので、この時はとても嬉しかったです。

ただそれでもスロークエリログを見ると依然として改善を入れた箇所が重そうだったので更なる改善ができないか試みました。
具体的には、SQL内で行われている複雑な計算を事前に行い、別テーブルにキャッシュさせることでクエリ内での計算をなくすことができないかを検討しました。

<details>

<summary>改善を試みた複雑なSQL</summary>

椅子の総移動距離を計算するSQLが非常に重いため、`chair_locations`テーブルにレコードを追加するタイミングで移動距離を計算し、その結果を別テーブルに保存することで効率化を図ろうとしました。
```sql
WITH
    relevant_chairs AS (
        SELECT
            id AS chair_id
        FROM
            chairs
        WHERE
            owner_id = ?
    ),
    distance_table AS (
        SELECT
            chair_id,
            SUM(distance) AS total_distance,
            MAX(created_at) AS total_distance_updated_at
        FROM
            (
                SELECT
                    chair_id,
                    created_at,
                    ABS(
                        latitude - LAG (latitude) OVER (
                            PARTITION BY
                                chair_id
                            ORDER BY
                                created_at
                        )
                    ) + ABS(
                        longitude - LAG (longitude) OVER (
                            PARTITION BY
                                chair_id
                            ORDER BY
                                created_at
                        )
                    ) AS distance
                FROM
                    chair_locations
                WHERE
                    chair_id IN (
                        SELECT
                            chair_id
                        FROM
                            relevant_chairs
                    )
            ) tmp
        GROUP BY
            chair_id
    )
SELECT
    c.id,
    c.owner_id,
    c.name,
    c.access_token,
    c.model,
    c.is_active,
    c.created_at,
    c.updated_at,
    COALESCE(dt.total_distance, 0) AS total_distance,
    dt.total_distance_updated_at
FROM
    chairs c
    LEFT JOIN distance_table dt ON dt.chair_id = c.id
WHERE
    c.owner_id = ?
```
</details>

ただこの改善はうまくいかず最終的には[Revert](https://github.com/ken-hashimoto/isucon14/commit/33c2645da592ec6d30473dd450c8fd975d7d134a)しました。
自分のやりたかった実装はできたはずなんですが、修正後にベンチマークを回した際の初期データチェックに失敗してしまいこれを解消することができませんでした。[^2]1時間半ほどトライした改善を諦めざるを得なかったのはショックでした :cry: 

[^2]: なぜレスポンスが変わってしまったのか、その原因は未だにわかってないです。

![スクリーンショット 2024-12-14 22.18.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/98dd6a29-fed4-930a-00f6-498c25b58a8b.png)



一方その間、@raorao1011は[静的ファイルをキャッシュするような対応](https://github.com/ken-hashimoto/isucon14/commit/da8ee9e7aa2e3eacd0e4bc72c2100b9a0273b099)を入れてくれたり、@kota33 は[より高速にjsonのデコード・エンコードを行うライブラリを導入](https://github.com/ken-hashimoto/isucon14/commit/0335d3f6883b82b44d016f91ae54cd02f206d425#diff-871eb89e86e63e7eca84f0075cba1a75574a11341cd89d39c7891864d2b085b9R13)してくれたりしていました。
またこのタイミングでprepared statementをオフにする対応も入れました。

15時から16時の間は、私の初期データチェックエラーが原因で0点が続きましたが、Revertした後にベンチマークを回すと4677点を取ることができました。
![貼り付けた画像_2024_12_14_22_07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/8a0e78ec-8a36-5012-8da1-481ece7ee6e3.png)


## 17:00 ~ 18:00 (5507点)
ISUCON終了まで残り1時間となったので、アプリケーションとDBのサーバー分割に取り組みました。
競技用インスタンスは3台用意されていましたが、それまで1台で全てのリクエストを処理しており、残りの2台はなにも活用されていない状態でした。このため、少なくともDBの処理を別のサーバーに移して負荷を分散させようと試みました。
サーバー分割はISUCONでは定番の改善手法ではあるのですが、準備不足のため手順がわからず、調べながら進める形になりました。見様見真似で実装したもののベンチマークを回したところ、初期走行でエラーが発生してしまい、残念ながらこの改善も断念することになりました。

![スクリーンショット 2024-12-14 22.43.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/bcdeaac8-2379-3a09-6420-635fb5dffb66.png)

また、この時間帯はログ出力の停止にも着手しました。
これまで出力されていたnginx、MySQL、アプリケーションのログを停止することでリソースの無駄を削減しました。この対応の結果スコアは5507点まで伸び、今回の最高記録を達成しました。[^3]
[^3]: 18時までの間での最高得点は5507点でしたが、最終結果の集計時に記録されたスコアは4701点だったみたいです。

![貼り付けた画像_2024_12_14_22_07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/304bef89-0dac-954e-82ae-114dae8ffef4.png)

## 18:00
競技が終了しました！

8時間は長いようで短く、まだまだ取り組みたかった改善が山ほど残っていましたが、終わった瞬間はまるで定期テストを終えた時のような清々しい気持ちになっていました。

競技終了後は、オフィスの別会議室でISUCONに参加していた他の方々と合流し、ISUCON公式の[YouTubeライブ](https://www.youtube.com/live/sT_9E0pTlns?si=P4Of3-5rA4rUy27v&t=32278)をみんなで視聴し問題解説などを聞きました。その後は居酒屋でお疲れ様会が開かれ、楽しいひと時を過ごしました。
そこで聞いた他チームが行った改善内容や、負荷計測に使用したツールの話はどれも興味深く、大変勉強になりました。また「次回こそもっといい順位を目指そう！」とモチベーションがさらに高まる時間でもありました。


# 反省と振り返り
ここまで、ISUCON当日の状況を時系列順に振り返ってきました。
ここからは今年のISUCONを「Good :thumbsup: 」と「More :wrench: 」に分けて総括し、来年への課題として活かしていきたいと思います。
## Good :thumbsup: 
### 初動の環境のセットアップがスムーズに行えた

去年の反省を活かして行った前日の準備が功を奏し、各種ツールのインストールやGit管理下に各設定ファイルを置く作業は30分ほどで終えることができました。このおかげで、早い段階から本格的な改善に集中することができました。

### 計測結果をチームに共有できた

スロークエリログや alp の結果を、1コマンドでDiscord経由で共有できる仕組みを構築したことは非常に有用でした。コピペ作業の手間を省けただけでなく、脳のリソースを無駄に消費することがなかったのも良かった点です。また、Discordに投稿された負荷状況をメンバー全員がリアルタイムで確認し、それぞれ改善案を考えられたことも大きな成果でした。

### ISUCONの基本である計測→改善→計測のサイクルを回せた

これが一番嬉しかった点です。ISUCONの基本ともいえる「計測→改善→計測」のサイクルを回せたことで、「ISUCONやってる感」をしっかり味わうことができました。去年は指をくわえて見ている時間が多かっただけに、今年はチームとしても、自分自身としても成長を感じる大会になりました。
## More :wrench: 
### Makefileの作り込みが甘かった
ログローテーションがうまくいかない問題を解消するのに約30分を費やしてしまい、非常にもったいなかったと感じました。この部分は事前に準備できる箇所だったので、当日躓かないようにしておくべきでした。
### 改善の穴にはまり、時間を無駄にしてしまった
お昼ごはん後に取り組んだ複雑なSQLの改善ですが、もう少し早いタイミングで撤退を決断しても良かったと反省しています。実装を進めるにつれ、後に引けない感覚に陥り、「もう少しだけ」とズルズル時間を浪費してしまいました。

他にもN+1問題など、改善すべき箇所は残っていたため、難易度やコスパを考慮して別の改善に移る判断を早めに下すべきでした。

### マニュアルを読む時間が取れなかった

競技開始と同時に公開されたマニュアルをじっくり読む時間を確保できませんでした。最終的にお昼後の14時半ごろに全体を軽く読んだのですが、マニュアルにはパフォーマンスチューニングのできる余地なども書かれていたため、序盤で読んでおけばより効率的に改善を進められたと反省しています。

### もう少し軽率にIndexを貼っても良かったかも
今回のISUCONでは、スロークエリの上位に来ているクエリだけを見てひとつずつインデックスを貼っていきましたが、ここはもうちょっと軽率にインデックスを貼ってもよかったかもと思いました。
もちろん業務でインデックスを貼る場合は慎重に行うべきですが、ISUCONという限られた時間の中でパフォーマンスチューニングを行う際にはもっと軽率に複数のカラムへインデックスを貼り、最初の30分程度で一通りの対応を済ませても良かったと感じました。

### 修正してベンチを回すまでの時間がもったいなかった
今回の改善プロセスでは
1. GitHubに修正PRを作成
1. mainにマージする
1. 各インスタンスで`git pull origin main`をしてからベンチマーク実行

という手順を踏んでいました。ただこのプロセスでは改善を取り入れてベンチマークを回すまでの手間が大きく、さらにエラーが発生した場合は再びPRを作る必要があり、非効率でした。
結構それが面倒だったので、もう競技用のインスタンスにsshして直接コードを編集しても良かったなと思いました。
せっかくインスタンスが複数台あるので、次回は競技用インスタンスに直接SSHでログインし、各自が別々のインスタンスでコードを編集・ビルド・ベンチマークを実行する方式を試したいです。うまくいった場合にのみGitHubにPRを出す、という流れにすることで改善のサイクルがスムーズになると感じました。

### やはり準備が大事だ
**これに尽きます！上記のすべての反省点は「準備不足」に起因していると言っても過言ではありません。**
今年は去年に比べて準備はしていたもののそれでもやはり準備不足を感じずにはいられませんでした。
特にサーバー分割は一度手を動かして事前に練習しておくべきだったなと思います。これをするだけでだいぶスコアが伸びたとあとから聞いたのでやらなかったことが悔やまれます。

# 最後に

来年はもっと準備を重ねて挑みたいと思います、ここまで読んでいただきありがとうございました！
ISUCON当日の競技用リポジトリを公開しているので興味のある方はご覧ください。

https://github.com/ken-hashimoto/isucon14

## PR
株式会社HRBrainでは、一緒に働く仲間を募集しています！
興味を持っていただけた方はぜひ弊社の採用ページをご確認ください！

https://www.hrbrain.co.jp/recruit