【ISUCON14】ISUCONに初参加しました

# はじめに

本記事は[フューチャー Advent Calendar 2024](https://qiita.com/advent-calendar/2024/future)の20日目 兼 [ISUCON14関連エントリ](https://isucon.net/archives/58835757.html)です。また本記事は私、[@utibori-jp](https://qiita.com/utibori-jp)、[@daigobase123](https://qiita.com/daigobase123)の共著です。

弊社24年度新人同期で「未来太郎」という名前のチームを組んで12/8(日)開催のISUCON14に参加しました。
メンバーは私と [@utibori-jp](https://qiita.com/utibori-jp)、[@daigobase123](https://qiita.com/daigobase123) で、メンバー全員ISUCON初参加でした。

結果は831チーム中499位と、目標としていた半分以上の順位に届かず悔しい結果となりました。

スコア遷移は以下の通りです。14:00~17:00ぐらいは全員バグが取れずに苦しんでいましたが、最後かろうじて持ち直すことができました。

![https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2608343/4b1cd93b-1c61-ecf3-6a1f-fb438c7b4f3f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2608343/4b1cd93b-1c61-ecf3-6a1f-fb438c7b4f3f.png)

# そもそもISUCONって何？

『ISUCON（イスコン）』とは『Iikanjini Speed Up Contest（いい感じに スピードアップ コンテスト）』を略したイベント名で、Webサービスをどれだけ高速化できるかを競うイベントです。
フロントエンドは基本的に触ることができない一方で、バックエンド・インフラについては、与えられたサーバーを使う以外はほとんど制約がなく、「速くなるなら、何をやってもいい」という自由度の高さが、このコンテストの特徴です。

# ISUCONに向けて準備したこと

ISUCONコミュニティは、取り組みを積極的に共有していこうという機運が活発で、ISUCONを始めるにあたり、何を準備すればよいのか、何を学べばよいのかという情報を多くの人が発信してくれています。

一通り調べて、ひとまず今年は **「ISUCON本で基本手法を学びながら、private-isuをやってみる」** 方針で準備を進めていくことにしました

ISUCON本とは、「達人が教えるWebパフォーマンスチューニング〜ISUCONから学ぶ高速化の実践」のことで、ISUCONを題材にWebアプリケーションのパフォーマンスを上げるのかについて解説している書籍です。

https://gihyo.jp/book/2022/978-4-297-12846-3

private-isuとは、Pixivが開催した社内ISUCONのAMIを公開してくれているものです。ISUCON本も、このprivate-isuを題材に解説をしており、ハンズオン形式で準備を進められるだろうということで、こちらに取り組んでみることにしました。

https://github.com/catatsuy/private-isu

以下の理由から言語は**Go言語** を選択しました。

* goroutineによって高速な並列処理が比較的簡単に実装できるため
* ISUCON参加者の多くがGoを利用しており、記事などの情報が多いため

https://qiita.com/shibukawa/items/e8b197a6f85bf8270cfb

## private-isuをやってみる

### 環境構築をする

private-isuに取り組むにあたり、ざっくり以下の方針で進められるように準備をしました。

- サーバーはEC2インスタンス上に立てて、操作はssh接続して行う
- ソースコードの編集はローカル環境で行い、Gitを使って管理する

### サーバーを立ててssh接続する

private-isuは、AMIが公開されているので、それを使って簡単にEC2インスタンスを作成できます。インスタンスタイプは、private-isuのREADMEで推奨されていたc7a.largeを使用しました。

余談ですが、AWSは、アカウント登録後1年は一部インスタンスが無料で使えるのですが、c7a.largeはその対象ではなくお金がかかります。
Dockerで環境を作ってインスタンス代をケチる案も出たのですが、以下理由からAWSを使って練習を行う方針にしました。

- お金がかかるとはいえ微々たるもの（3人で割るからなおさら）
- 本番となるべく近い環境でやりたい

サーバーを立てたあとは、以下コマンドで簡単にssh接続できます。

```shell
ssh -i /path/to/your/key.pem username@hostname_or_ip_address
```

:::note
ちなみにVSCodeからssh接続する方法もありますが、こちらを使用するとインスタンスのCPU負荷が高くなり、頻繁に接続が切れるようになりました。
VSCodeの設定変更で緩和することはできますが、今回はVSCodeのssh接続は使わない方針にしました。

- [Visual Studio CodeでのSSH接続により、EC2サーバーが高負荷になり動かなくなった - エキサイト TechBlog.](https://tech.excite.co.jp/entry/2022/09/27/153341)
:::

### ソースコードをGit管理する

さすがに生のコードを3人同時編集するのは危なすぎるので、ソースコードはGit管理しました。ssh経由での接続は初めてであることと、権限周りが若干ややこしいことから結構苦戦し、初日集まったときは3時間くらいかかりました。

Git管理の方針として、webapp配下のみをGit管理するという人が多いようですが、nginxやmysqlの設定ファイルも残しておきたいよねということで、rootディレクトに`.git`を配置することにしました。

ただそのままだと、画像ファイル等の大きいファイルをgit上にアップロードしてしまうことになるので、以下コマンドでgit管理対象ファイルを絞り込むようにしました。

```shell
sudo find / -size +10M -type f >> .gitignore
```

### ボトルネックを解析する
ISUCONのソースコードを実際に見てみると、「ここ改善できそう」という箇所が無数に見つかります。
しかし全ての箇所を改善する時間はないので、その中から優先順位をつけて効率的にパフォーマンスを改善する必要があります。

今回はボトルネックの解析手法として、以下4つを選択しました。

- ab、alpによるアクセスログの解析
- htopコマンドによるCPU負荷の解析
- slow-query-log(pt-query-digest)によるMySQLの解析
- pprofによる、Goの各関数の実行時間の解析

上3つはISUCON本で紹介されているのですが、pprofはISUCON本には載っておらず、過去の参加者のISUCON攻略記事を読んでいた際に存在を知りました。

https://zenn.dev/team_soda/articles/d4701665e8a3a7

pprofでは、localhostを使って、出力結果をブラウザからインタラクティブに操作できるのですが、環境がssh接続したEC2インスタンスサーバーであるため、準備に少し苦戦しました。
最終的にはポートフォワードを行って使用しました。

```
ssh -i /path/to/your/key.pem -L 8080:localhost:8080
username@hostname_or_ip_address
```

ISUCONでは、pproteinというISUCON専用計測ツールがあり、実際上位者はほぼ全員使っているらしいです。しかしぱっと見でツールの価値がわからず、今年はいったん見送りとなりました。
来年は「pproteinは神ツール！」といえるレベルまで行きたいです。

### 高速化手法を一通り試してみる

主なボトルネック解析手法を確認した後は、キャッシュやリバースプロキシ、各種データベースチューニング等を実際にやってみました。

リバースプロキシは、ISUCON本で紹介はされているものの、具体的な手法は載っていませんでした。しかし、他の参加者の方が、private-isuでのリバースプロキシの実装例を投稿してくださっていたので、そちらを参考に進めることができました。

https://speakerdeck.com/sadnessojisan/isuconru-men-yi-qian-isunarabe-lt-number-1?slide=56

上記手法を一通り試したあと、ISUCON本の付録で「private-isuの攻略実践」という章があり、そちらをハンズオンでやってみようとなったのですが、途中で寄り道しまくった結果あまり試せませんでした。

とはいえ、環境構築、計測手法、改善手法の確認を無事コンテストまでにやり切ったうえで、ISUCON14の当日に臨むことができました。

# 当日やったこと

当日は、ミドルウェアの**設定変更**、**サーバー構成 & アプリケーションの理解**、**ログ解析**、**チューニング** の4つを行いました。

### ミドルウェアの**設定変更**

private-isuでの練習内容を元に、**MySQL**、**Nginx**の設定を行いました。
また、Goアプリケーションのビルドやサービスの再起動を行うためのシェルスクリプトも、開始直後に作成しました。

### サーバー構成&アプリケーションの理解

Gitやログ周りの設定が完了した段階で、サーバー構成とアプリケーションの理解を進めました。

- **アプリケーションマニュアル**
    
    [ISUCON アプリケーションマニュアル](https://gist.github.com/wtks/8eadf471daf7cb59942de02273ce7884)が配布されており、これを参考に大まかな仕様を把握しました。今年の課題はライドシェアサービス **「ISURIDE」** のチューニングでした。
    
    アプリケーションの画面操作と **OpenAPIの仕様書** を照らし合わせながら理解を進めました。

    ![client-application-simulator-image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2608343/c53c96b4-d32b-17e8-4bc3-6cb99a192914.png)
    
- **サーバー構成の理解**
    
    サーバー構成はシンプルで、App、Nginx、MySQLが同一EC2インスタンス上に配置されていました。
    
    - **アプリケーション (App)**
    - **Nginx**
    - **MySQL**

### ログ解析

ログ解析には **pt-query-digest** と **alp** を用いました。修正が必要なSQLクエリの優先度を定めました。

### チューニング

#### 1. マッチングロジックの修正

リファレンス実装（修正前）では、最も待たせているリクエストに対して適当な空いている椅子を割り当てるロジックとなっていました。

そこで、**乗車位置に近い椅子を優先して割り当てるロジック** に修正しましたが、マッチングできないユーザーが現れ、得点が0になるエラーが発生しました。コンペ終了時まで解消できなかったため、この部分の修正を破棄しました。

#### 2. 椅子一覧取得ロジックの修正

リファレンス実装（修正前）では、`chair_locations`テーブルに椅子の移動履歴が記録されており、それをもとに各椅子の総移動距離を計算し、`chairs`テーブルの`total_distance`カラムとして取得するSQLクエリを発行していました。

しかし、この方法ではSQLを発行するたびに総移動距離を計算する必要があるため、各椅子の総移動距離を管理するテーブルを新たに追加しました。そして、`chair_locations`に**insert**を行うタイミングで、移動距離を管理するテーブルも同時に更新するように変更しました。

```sql
--リファレンス実装(椅子一覧取得)
SELECT 
　id,
  owner_id,
  name,
  access_token,
  model,
  is_active,
  created_at,
  updated_at,
  IFNULL(total_distance, N) AS total_distance,
  total_distance_updated_at
FROM chairs LEFT JOIN (
  SELECT 
    chair_id,
    SUM(IFNULL(distance, N)) 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
  ) tmp
  GROUP BY chair_id
) distance_table ON distance_table.chair_id = chairs.id
WHERE owner_id = 'S'
```

#### 3. テーブルにインデックスを張る

レコード数の多いテーブルおよび結合対象のテーブルにインデックスを追加しました。

#### 4. 通知APIの間隔調整

ユーザーと椅子の情報通知は、デフォルトで **30ms** 間隔になっていました。短い間隔では情報の変化が少なく、無駄な処理が発生していました。

通知間隔を**500ms** にすることで、不要な処理を削減しました。

# 全体の反省

## 大きな変更を恐れすぎていた

バグの発生を恐れて大きな変更を避けた結果、逆に実装が難しくなるという状態に陥ってしまっていました。
例えばマッチングロジックの修正の実装において、マッチングロジックを担うSQL文だけを変更しようとした結果、SQLが非常に長くなり、最後までバグを解消できませんでした。しかし、コンペ終了後話し合った際に、テーブル定義自体を変更することで簡単に実装できることが判明しました。

明らかにボトルネックと判明している箇所においては、設計の変更を伴う大きな変更が結果的に近道になりうるということを学びました。

## 練習不足

### 時間配分の感覚不足

事前の練習では3日間かけて1つの問題(private-isu)を解く練習をしていたため、1つのボトルネックにどれだけ時間をかけるべきかの感覚を養えていませんでした。
来年のISCUONまでに、過去問を1日かけて解く勉強会を定期的に開催したいと考えています。

### private-isuへのオーバーフィット

private-isuを数日間かけて解いた以外には練習を行なっていなかったため、private-isuにオーバーフィットしてしまっていました。

private-isuでは実行バイナリの名前がappなのですが、今回は実行バイナリの名前がisurideでした。private-isu用に作成した、ビルドとnginxの再起動を行うスクリプトを本番でも使用しましたが、実行バイナリ名が違うことに全員気づかずスクリプトを使用していました。
そのため、ソースコードを変更しても一切スコアが変動しない現象に陥ってしまい、3時間苦戦しました。

他の過去問を解いていたら、もう少し早く原因追及が行えたかもしれないと反省しています。

# 個人の感想

## @utibori-jp

まず何より、一緒にISUCONに参加してくれた@ppputtyoと@daigobase123に感謝を述べたいです。

今回のチームは、私がISUCONに挑戦したいと考え、同期に声をかけて結成したチームです。当初はメンバー集めに苦労するだろうと思っていましたが、実際には初めに声をかけた二人が快く参加してくれ、あっさりとチームが決まりました。大学時代にも、チームでコンテストに出たいと思っていたものの、なかなかメンバーを集められなかった経験があったので、同じモチベーションでコンテストに挑める仲間がすぐに見つかるFutureという環境には、とても恵まれていると感じています。

初出場は悔しい結果に終わってしまいましたが、ISUCONに参加しなければ出会えなかった技術に触れることができ、とても充実した時間でした。引き続き自己研鑽に励み、2年後シャボンディ諸島に集まった時くらいパワーアップした姿で、来年再集結しようと思います。

## @ppputtyo

誘ってくれた@utibori-jpと一緒に参加してくれた@daigobase123に非常に感謝しています。

最近はプログラミングコンテストから離れ気味でしたが、今回のISUCONで改めてプログラミングコンテストの楽しさを思い出しました。
また、チームで議論しながら練習・勉強できたのが非常に楽しかったです。(ログの解析方法で@utibori-jpとホワイトボードを使って議論したのが印象に残っています。笑)

練習していたことが出しきれず悔しい気持ちもありますが、総じて楽しく、あまり勉強したことがない分野を実践を通して学べたので非常に良い経験になりました。

来年またこの3人で出場して、今度こそ上位を狙いたいと思います！！！

## @daigobase123

ISUCON参加のきっかけを作ってくれた@utibori-jpに感謝を述べたいです。

勉強会で議論を重ねる中で、理解が深まっていくことを実感できました。

ISUCONは触れるべき範囲が広いため、コンペ本番までの準備が最低限のものになってしまったのが心残りです。

しかし、ISUCONの感覚を掴むことができたと思います。来年は、ワンスクロール以内に自分たちのチーム名を載せられるよう頑張りたいと思います。

# おわりに

悔しい結果に終わりましたが非常に楽しかったです！
最後に、ISUCONを主催していただいているLINEヤフー様、そしてスポンサー企業の皆様に深く感謝申し上げます。