# traP ISUCON初心者向け講習会
これは2022/7/9にtraP内で開催した講習会の資料を外部公開に際して一部変更をしたものです
[ISUCON初心者向け講習会を開催しました | 東京工業大学デジタル創作同好会traP](https://trap.jp/post/1614/)
## 目標
- 予選本番で1つ以上の改善を行えるようになる
- 公式解説や参加者writeupを読んで理解できる/実践できるようになるための基礎知識を学ぶ
※優勝したり上位に入るためのテクニックは扱いません
## 参加する上での気持ちについて
- 初参加で完璧にやろうと気負わなくていい
- 本番でなんらかの改善を入れて点数を上げられれば十分すごい!
- 最近は学生枠での本戦出場もすごく難しい……
「やったことがないことはやらない」は**勝つため**には重要
しかし、ISUCON初参加/web初心者は **「やったことがない」にぶつかるのは当たり前**
大会の時間中に「やったことはないけれど、これで改善できるかもしれない」まで至れるのはよいこと
はじめてのことを試してみて、それで学びを得られたら十分参加の意義がある
## チーム連携
### 対面orリモート
どちらでもOK
チームで相談して選ぼう
#### 対面
同じ部屋に他チームもいるのはダメなので要注意
メリット
- すぐに相談できる
- 画面を見せての相談も簡単
- 「行き詰まってそうだな」というのがわかりやすい
- 泥沼に陥ってて自力じゃ切り替えれない、時間配分的な見切りができないということはありがち
#### リモート
各自の自宅から通話をつなぎながら
各チームでDiscordやSlackを立てる必要がある
メリット
- 慣れた環境で作業できる
- モニター、キーボード、etc.
### 情報の共有場所
口頭での情報共有以外に、テキストで共有できる場所があるとよい
- 同時編集での共有
- 問題についての調査やボトルネック候補、試した改善手法の共有など
- [HackMD](https://hackmd.io/)
- 時間情報込みでの共有
- 計測したログやスコアの共有など
- SlackやDiscordのチャット、GitHubリポジトリのissue
### 役割分担
チームによって様々なので「正解」はない
ここではよくある(?)パターンを紹介するので、チーム内で方針を決めておくとよいです
#### チームメイト不要! 1人で完璧に戦えます!
玄人向け
常連チームでもそうそういない
- to-hutohuさん(traP OB)
- ISUCON9 予選1日目で1位を獲得(予選全体では2位)
- [ISUCON9予選1日目で最高スコアを出しました | とーふとふのブログ](https://to-hutohu.com/2019/09/09/isucon9-qual/#%E8%BE%9E%E9%80%80%E3%81%97%E3%81%9F%E7%90%86%E7%94%B1)
- takonomuraさん(ISUCON11で運営参加)
- ISUCON9 予選2日目/全体で1位を獲得
- [ISUCON9 予選を全体1位で突破しました | takono.io](https://www.takono.io/posts/2019/09/isucon/)
- ISUCON10 本選で1位を獲得
- https://twitter.com/takonomura/status/1312350375920332800
どちらもISUCONの過去問練習、事前準備をがっつりやりこんでいる人
#### 各自でゴリゴリ進めていく ソロプレイヤーの集合体
常連チームではそこそこいる
メンバー全員が各自で計測、分析、改善を行っていくスタイル
作業箇所が競合しないようにの管理、厳しい時の相談、タスクの受け渡しはちゃんとやっている
チームメンバー全員のレベルが高い&お互いの能力・思考がわかりあっている前提
#### 役割分担をして堅実なチームプレイ
初参加ではこれがおすすめ
「役割分担」といっても大まかなゆるっとしたもの
- 環境操作・計測担当x1
- 本番環境の操作は基本的にすべてこの人が担当する
- 本番環境での作業コンフリクトや手順忘れを防げる
- 計測結果からの具体的なボトルネック特定までできるのが理想
- 手が空いている時は改善に参加する
- 改善専任担当x2
- ボトルネックの改善作業に専念する
- ボトルネックの種類によってはガッツリ実装を書く必要があることもある
- チームによってはさらに細かくDB担当・アプリ担当などに分けていることもある
- 実際は「そのときのボトルネック」に合わせていじるべき箇所が変わるので、細かい担当の固定化はあまり意味がない
- 「より得意な領域」を共有しておくと、見つけたボトルネックの担当割り振りがやりやすい
- マニュアル完全理解担当(他と兼任)
- マニュアルをしっかり読みこんで理解しておく
- 内容を覚える必要はないです 索引になれるくらいが理想
- 「8時間で全てのボトルネックを解消する」のは無理なので、優先順位付けの指針にマニュアルが超重要
- 「やっていいこと/ダメなこと」「スコアにつながりそうな改善/つながらない改善」をチームメンバーに共有・指摘していく
## 事前準備
### Linuxの操作に慣れる
本番中は基本的に、Linuxコマンドを叩いて環境を操作しなければならない
#### [Linux入門の入門 | さくら, 茗荷 |本 | 通販 | Amazon](https://www.amazon.co.jp/Linux%E5%85%A5%E9%96%80%E3%81%AE%E5%85%A5%E9%96%80-%E8%8C%97%E8%8D%B7-%E3%81%95%E3%81%8F%E3%82%89/dp/4873100941)
よく使うコマンドがまとまった小冊子
「こういう操作をしたいけれどなんのコマンドを使えばいいかわからない」というときは、この本をペラペラ眺めてみると良い
#### [Hacknet | Steam](https://store.steampowered.com/app/365450/Hacknet/?l=japanese)
ハッカーとしてサーバー上で操作をして進めていくゲーム
一部なんちゃってなコマンドもあるけれど、基本的な操作はLinuxコマンドと大体同じ
「コマンドを打つとめっちゃ文字が流れてくる」ことに慣れるのにおすすめ
#### [Bandit | OverTheWire](https://overthewire.org/wargames/bandit/)
実際にLinuxコマンドを使いながら問題を解いていく
全てのコマンドを覚える必要はないけれど、「こういうことができるコマンドがある」と知っておけるのは大事
### GitHubのリポジトリを作っておく
ソースコードの管理・共有用のリポジトリは事前に作っておこう
必ず**privateリポジトリとして作成する**こと
競技中にpublicリポジトリに問題のソースコードをアップしてしまうと、失格になる可能性があります
> 以下の行為を特に禁止する。
> - 予選の競技終了時間までに、競技の内容に関するあらゆる事項 (問題内容・計測ツールの計測方法など)を公開・共有すること(内容を推察できる発言も含む)
>
> [ISUCON11 予選レギュレーション : ISUCON公式Blog](https://isucon.net/archives/55854734.html)
個人のprivateリポジトリを作って他メンバーをCollaboratorとして招待する形でOK
無料ユーザーの個人privateリポジトリは招待できる人数上限があるが、ISUCONチームメンバーの3人は問題なく招待できる
### よく使うコマンドをスクリプトにまとめておく
ベンチマークを回す前にやることとか
結構いろんな操作が必要なので、都度手打ちの実行をするのは厳しい
「ISUCON Makefile」で調べるといろんな人が公開しているのが出てくる
[ハンズオンで使うMakefile](https://github.com/oribe1115/traP-isucon-newbie-handson2022/blob/main/Makefile)も主要な操作をまとめてあります
### チートシートを作っておく
「やることに困ったらここを見る」ができるように
特に「競技が始まったらまずやること」はまとめておこう
チートシートもいろんな人が公開しているので参考にしてみるとよい
---
## ここからハンズオン
ISUCON11予選(PISCON1回目の問題)を使います
[oribe1115/traP-isucon-newbie-handson2022](https://github.com/oribe1115/traP-isucon-newbie-handson2022)
このリポジトリをテンプレートにしてリポジトリを作成しておいてください
今回はpublic/privateどちらでもOK
[テンプレートからリポジトリを作成する - GitHub Docs](https://docs.github.com/ja/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template)
:::warning
上記のテンプレートリポジトリは今回のハンズオンに特化した内容になってます
今後使用してもらってもOKですが、他の問題ではそのまま使えないことに注意してください
**含まれている問題**
- ISUCON11予選の環境を前提とした設定ファイル
- 構成管理ツールを使わずにある程度の構成管理をするためのやや無理矢理な構成
:::
oribeがデモをするリポジトリ: https://github.com/oribe1115/traP-isucon-newbie-handson2022-demo
## 最初にやること
### ベンチマークを回す
インスタンスが立ったら何はともあれベンチマークを回す
SSHで入るよりも前にやる勢いでよい
目的
- 最初の「何も触っていない状態でのスコア」の確認
- インスタンスが正常に稼働しているかの確認
- この状態でベンチマークが通らなければ、競技環境に問題がある可能性が高い
### ツールの導入
以下、コマンドは基本的にサーバー上のホームディレクトリ(`/home/isucon`)で実行してください
#### Makefileの取得
```shell=
curl https://raw.githubusercontent.com/oribe1115/traP-isucon-newbie-handson2022/main/Makefile -o Makefile
```
#### ツールのインストール、デプロイキー用の鍵の生成
```shell=
make setup
```
適宜`yes`やEnterを押して進める
`ssh-keygen`で生成する鍵のパスフレーズは空でよいので、Enterを押して進める
### git管理
#### デプロイキーの設定
privateリポジトリとのやりとりを円滑に行うためにデプロイキーを使用する
作成したリポジトリのSettings > Deploy Keysで登録する
登録する公開鍵はサーバー上での以下の出力結果
```shell=
cat .ssh/id_ed25519.pub
```
[デプロイキーの管理 - GitHub Docs](https://docs.github.com/ja/developers/overview/managing-deploy-keys#deploy-keys)
#### リポジトリからの取得
```shell=
git init
git remote add origin {作成したリポジトリのSSH用URL}
```

```shell=
git pull origin main
```
:::spoiler git pullでエラーが出る時は
```
error: The following untracked working tree files would be overwritten by merge:
Makefile
Please move or remove them before you merge.
Aborting
```
上記のエラーが出た場合は以下のコマンドでMakefileを削除してからpullをやり直してみてください
```shell=
rm Makefile
```
:::
#### .gitignoreの設定
今回は設定済みなのでスキップ
必要のないファイル、大容量なファイル(DBの初期データなど)をリポジトリに含めてしまわないよう、適切に設定する必要がある
#### リポートリポジトリへのpush
```shell=
git branch -m main
make set-as-s1
make get-conf
git add .
git commit -m "init"
git push origin main
```
## 問題の把握
### 出題動画を見る
[ISUCON11 予選問題 - Youtube](https://youtu.be/P-iJ01-riTw)
競技のライブ配信で、競技開始時刻よりも前に流れ終わるように流れるので焦らずに見ておくとよい
ボトルネックや改善の仕方そのものの情報は出てこないが、アプリについてのドメイン知識の把握に有用
:::spoiler 出題動画について
出題動画が用意されるようになったのはここ最近になってからです
予選の出題動画が用意されたのはISUCON11が初です
評判良かったし、多分今後も継続して用意されると思われます
:::
### マニュアルを読む
#### [ISUCON11 予選当日マニュアル](https://github.com/isucon/isucon11-qualify/blob/main/docs/manual.md)
ルールや環境の説明について書かれている
特に改善方針の決定に重要な箇所
- [負荷走行について](https://github.com/isucon/isucon11-qualify/blob/main/docs/manual.md#%E8%B2%A0%E8%8D%B7%E8%B5%B0%E8%A1%8C%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)
- [スコア計算](https://github.com/isucon/isucon11-qualify/blob/main/docs/manual.md#%E3%82%B9%E3%82%B3%E3%82%A2%E8%A8%88%E7%AE%97)
#### [ISUCONDITION アプリケーションマニュアル](https://github.com/isucon/isucon11-qualify/blob/main/docs/isucondition.md)
問題のアプリ自体の説明が書かれている
アプリ独自の用語や機能を確認する
### serviceの確認
動いているアプリケーションが何かを確認する
```shell=
sudo systemctl list-units --type=service
```
`s1/etc/systemd/system/isucondition.go.service`を見てみる
### テーブル構成を見る
`webapp/sql/0_Schema.sql`
こんな感じのデータを持ってるんだな〜とざっくり把握できればいい
`isu.image`がLONGBLOB型で画像データがそのまま入ってそうでやばそうだけど、本当に**ボトルネックなのかはまだわからないので触らない**
### ソースコードをざっと見る
`webapp/go/main.go`
明らかに分量がやばいので頭から読もうとしない
ざっと「こんな関数があるんだな〜」って把握すれば十分
N+1っぽい多重for文があっても**ボトルネックかはまだわからないので触らない**
main関数見てAPIの構成を把握しておくのはおすすめ
alpの設定にも必要な情報なので
`postInitialize`もざっと見ておくと良い
## 計測ツール
### top
プロセスごとのcpu使用率、メモリ使用率をリアルタイムで確認できる
#### 使い方
`top`
### dstat
サーバーのリソースの使用具合をリアルタイムで確認できる
(開発終了してるらしいので別のに乗り換えた方がいいのかもしれない)
#### 使い方
`dstat`
### journalctl
systemdで動いているサービスのログを確認できる
#### 使い方
`make bench`を実行すると、最後にjournalctlが立ち上がった状態になる
### alp
[tkuchiki/alp: Access Log Profiler](https://github.com/tkuchiki/alp)
アクセスログを解析するツール
オプション指定で同種のリクエスト(e.g. URI内のIDだけ違う)を束ねることができるので便利
どのAPIにどれくらいリクエストが来ていて、それがどのくらい重いのかを調べるのに使う
#### 使い方
`make alp`
#### 設定の追加
`s1/etc/nginx/nginx.conf`
```diff=
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
+ log_format ltsv "time:$time_local"
+ "\thost:$remote_addr"
+ "\tforwardedfor:$http_x_forwarded_for"
+ "\treq:$request"
+ "\tstatus:$status"
+ "\tmethod:$request_method"
+ "\turi:$request_uri"
+ "\tsize:$body_bytes_sent"
+ "\treferer:$http_referer"
+ "\tua:$http_user_agent"
+ "\treqtime:$request_time"
+ "\tcache:$upstream_http_x_cache"
+ "\truntime:$upstream_http_x_runtime"
+ "\tapptime:$upstream_response_time"
+ "\tvhost:$host";
- access_log /var/log/nginx/access.log main;
+ access_log /var/log/nginx/access.log ltsv;
```
コピペ元: https://github.com/tkuchiki/alp#nginx
alpでの出力用の設定は`tool-config/alp/config.yml`
### pprof
[pprof package - net/http/pprof - Go Packages](https://pkg.go.dev/net/http/pprof)
Go言語用のプロファイリング可視化ツール
アプリケーション内でどこにどのくらい時間がかかっているのかを確認するのに使う
#### 使い方
ベンチマーク中に`make pprof-record`で計測
`make pprof-check`でWebUIによる閲覧
WebUIでの閲覧時にはポートフォワードが必要
```shell=
# ローカルで実行
ssh -L 8090:localhost:8090 isucon@{グローバルIP}
```
#### 設定の追加
`webapp/go/main.go`
import文に追加
```go
import (
_ "net/http/pprof"
...
)
```
main関数に追加
```go
func main() {
go func() {
log.Fatal(http.ListenAndServe(":6060", nil))
}()
...
}
```
### pt-query-digest
[pt-query-digest](https://www.percona.com/doc/percona-toolkit/LATEST/pt-query-digest.html)
スロークエリーログの解析に使う
スロークエリーログ: DBで実行されたクエリのうち実行に時間がかかったもののログ
#### 使い方
`make slow-query`
#### 設定の追加
`s1/etc/ysql/mariadb.conf.d/50-server.cnf`
```diff=64
- #slow_query_log_file = /var/log/mysql/mariadb-slow.log
+ slow_query_log_file = /var/log/mysql/mariadb-slow.log
- #long_query_time = 10
+ long_query_time = 0
```
```diff=128
[mariadb]
+ slow_query_log
```
## 改善
実演
参考: [ISUCON11 予選問題実践攻略法](https://isucon.net/archives/56082639.html)
ベンチマークを回す前には必ず`make bench`を実行する
## [おまけ]複数台構成に切り替える(App + DB)
### 2台目での環境構築
リモートリポジトリの設定までは[ツールの導入](#ツールの導入)と同じ
pullをすると以下のエラーが出てしまうので
```
error: The following untracked working tree files would be overwritten by merge:
Makefile
webapp/.gitignore
webapp/NoImage.jpg
webapp/ec256-public.pem
...
```
これで解決(力技)
```shell=
git fetch origin main
git reset --hard FETCH_HEAD
```
```shell=
git branch -m main
make set-as-s2
make get-conf
git add .
git commit -m "s2 init"
git push origin main
```
### ユーザーの追加
2台目で操作
`make access-db`でデータベースに入れる
```=
# データベース接続ユーザーの確認
mysql> SELECT host,user,password FROM mysql.user;
mysql> CREATE USER 'isucon'@'%' IDENTIFIED BY 'isucon';
mysql> GRANT ALL ON *.* TO 'isucon'@'%' WITH GRANT OPTION;
mysql> SELECT host,user,password FROM mysql.user;
```
:::spoiler CREATE USERによるユーザー追加の記法
```=
mysql> CREATE USER '{ユーザー名}'@'{許可するホスト}' IDENTIFIED BY '{パスワード}';
mysql> GRANT ALL ON *.* TO '{ユーザー名}'@'{許可する}' WITH GRANT OPTION;
```
ISUCONなのでワイルドカード(`%`)を使って任意のホストからのアクセスを許可する設定にしてしまってOK
DBのport(MariaDBのデフォルトportは3306)を外部に対して開けていなければ攻撃される心配はないです
:::
#### 設定を変更
`s2/etc/mysql/mariadb.conf.d/50-server.cnf`
```diff=26
# Instead of skip-networking the default is now to listen only on
# localhost which is more compatible and is not less secure.
- bind-address = 127.0.0.1
+ bind-address = 0.0.0.0
```
`s1/home/isucon/env.sh`
```diff=
- MYSQL_HOST="127.0.0.1"
+ MYSQL_HOST="{2台目のプライベートIPアドレス}"
MYSQL_PORT=3306
MYSQL_USER=isucon
MYSQL_DBNAME=isucondition
MYSQL_PASS=isucon
POST_ISUCONDITION_TARGET_BASE_URL="https://isucondition-1.t.isucon.dev"
SERVER_ID=s1
```
ベンチマークを回す前に両方のリポジトリを最新にする&`make bnech`を実行するのを忘れずに
## 参考資料
- [ISUCON11 予選問題実践攻略法 : ISUCON公式Blog](https://isucon.net/archives/56082639.html)
- [達人が教えるWebパフォーマンスチューニング 〜ISUCONから学ぶ高速化の実践|技術評論社](https://gihyo.jp/book/2022/978-4-297-12846-3)
- [narusejun/isucon11-qualify](https://github.com/narusejun/isucon11-qualify)
## 追記
2022/7/22: ハンズオンで使用するMakefileでログローテーションがうまく行えていなかった問題を修正しました