---
# System prepended metadata

title: ISUCON 11 予選

---

# ISUCON 11 予選

## 環境構築

-  SQL の Init ファイルをサーバに移す
-  SQL の便利コマンドライブラリをインストール

```
sudo apt install percona-toolkit
```

- alp をインストール

```
cd ~/
wget https://github.com/tkuchiki/alp/releases/download/v1.0.21/alp_linux_amd64.zip
unzip alp_linux_amd64.zip
sudo mv alp /usr/local/bin/
```

- goのツール、measureを入れ込む

各関数に下記のようなコードを追加するだけで、各関数の実行時間が観れるらしい -> https://github.com/najeira/measure


```
# インストール
go get github.com/najeira/measure
```


```
# 関数ごと
defer measure.Start("hoge").Stop()

# 範囲指定
m := measure.Start("foo")
// your code
m.Stop()
```

```
func main() {
    ...
    # get apiを仕込む
    mux.HandleFunc(pat.Get("/stats"), measureStats)
    ...
}

func postInitialize() {
    ...
    measure.Reset()
    ...
}

func measureStats(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query()
	key := q.Get("key")

	stats := measure.GetStats()
	stats.SortDesc(key)

	w.WriteHeader(http.StatusOK)

	fmt.Fprintf(w, "%-30s %-10s %-10s %-10s %-10s %-10s %-10s %-10s\n",
		"key", "count", "sum", "min", "max", "avg", "rate", "p95")
	for _, s := range stats {
		fmt.Fprintf(w, "%-30s %-10d %-10.2f %-10.2f %-10.2f %-10.2f %-10.2f %-10.2f\n",
			s.Key, s.Count, s.Sum, s.Min, s.Max, s.Avg, s.Rate, s.P95)
	}
}
```

```
watch -n 5 -t curl -X 'GET' 'http://localhost:8000/stats'

もしくは

make go-measure
```




## コマンド

### Redis 建てる

```bash
sudo apt-get update
sudo apt-get install redis-server -y
sudo systemctl enable redis-server.service
# bind 127.0.0.1 ::1 -> bind 0.0.0.0 に変更
# protected-mode no
sudo vi /etc/redis/redis.conf
sudo systemctl restart redis.service
```

### ssh-keygen

```bash
ssh-keygen -t ed25519
vi ~/.ssh/id_ed25519.pub
```

### git clone

```bash
rm -r webapp/
rm env.sh
git clone git@github.com:sato9818/isucon-11-qualify.git
mv isucon-11-qualify/* .
mv isucon-11-qualify/.git* .
rm -r isucon-11-qualify/
sudo systemctl start isucondition.go.service
```

### ログ確認

```bash
journalctl -r
```

### Nginxログ確認

```bash
sudo cat /var/log/nginx/access.log | alp ltsv
sudo rm /var/log/nginx/access.log && sudo systemctl reload nginx
```

## Nginx

### Install NginX

```
sudo apt update
sudo apt install nginx
```

### Configure NginX

```
sudo vim /etc/nginx/sites-available/isucondition.conf
```

```conf
upstream webapp {
    server localhost:3000;
    server 54.249.150.240:3000; 
}

server {
    listen 443 ssl http2;

    ssl_certificate /etc/nginx/certificates/tls-cert.pem;
    ssl_certificate_key /etc/nginx/certificates/tls-key.pem;

    location / {
        proxy_pass http://webapp;
    }
}
```


### Check Configuration and Restart NginX

Before restarting NginX, you should check if your configuration files have any syntax errors to avoid crashing the server.

```
sudo nginx -t
```

```
sudo systemctl restart nginx
```

## MySQL の分離

### bind-address の変更 on MySQL server

```
# Already ssh-ed mysql-server
sudo vim /etc/mysql/mariadb.conf.d/50-server.cnf
```

```
# Instead of skip-networking the default is now to listen only on
# localhost which is more compatible and is not less secure.
bind-address            = 0.0.0.0 # Changed from 127.0.0.1
```

```
sudo service mariadb restart
```

### private networkから接続するuserを作成する on MySQL server

- 当日のprivate networkをMakefileに設定する

```
PRIVATE_NETWORK=192.168.% # ここを任意のアドレスに
```

- userを作成するコマンドを実行する

```
make create-user
```

### mysqlホストの変更(mysqlに接続するサーバー側の設定)

mysqlのサーバーIPを設定

## 当日の流れ

- bench動かす
    - 動くはずらしい
    - スコア確認する

```
cd ~/webapp/ && make build && cd ~/ && sh wrap_bench.sh
```

- ボトルネックの特定
    - 計測ツールのコマンドを実行

```
htop
```

- MySQL がボトルネックの場合が多い

![Screenshot 2023-11-12 at 15.08.50](https://hackmd.io/_uploads/Hy_AhyAQp.jpg)

- Optional: Thread を表示しない設定にする
    - F2 -> Display options -> Hide userland process threads

### MySQL のボトルネックを解消

#### 発見
```
cd ~/webapp && make slow-on
make build
sh ~/wrap_bench.sh

make pt-query-digest

sudo less /tmp/pt-query-digest.txt
```

- Profileを見れば遅いクエリがわかる

![image](https://hackmd.io/_uploads/rJ_zZe0X6.png)

#### 解決策

- SLECT がボトルネックの場合
    - index を張る
        - where や order by で全探索しているのが原因のように見えるとき
    - n+1問題の解消
        - calls の桁数が明らかに多い
    - limitをつける
        - Go 上で `items[0]` しか使用していないことがわかるとき
    - いらない情報まで取得してる
        - where による絞り込みを強くする
- COMMIT がボトルネックの場合
    - pt-query digest からどの INSERT/UPDATE がボトルネックかを特定する
    - bulk insert を行う
        - for 文の中で insert を行っている場合
    - mysqlサーバーの分離
        - 最初からやってしまうというベストプラクティスでいいかもしれない

### アプリケーションのボトルネックを解消

- そのうち、ボトルネックがアプリケーションの方に移る
    - 左: Application サーバ, 右: MySQL サーバ
    - 左の方が90%を超えているが右は40-50%くらい

![Screenshot 2023-11-12 at 21.29.22](https://hackmd.io/_uploads/HJm9SBCmT.jpg)

- エンドポイントごとの実行時間を見てボトルネックを知る

#### alp のための設定

```
sudo vim /etc/nginx/nginx.conf
```

```
http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format json escape=json 
      '{"time":"$time_iso8601",'
      '"host":"$remote_addr",'
      '"port":"$remote_port",'
      '"method":"$request_method",'
      '"uri":"$request_uri",'
      '"status":"$status",'
      '"body_bytes":"$body_bytes_sent",'
      '"referer":"$http_referer",'
      '"ua":"$http_user_agent",'
      '"request_time":"$request_time",'
      '"response_time":"$upstream_response_time"}';

    access_log /var/log/nginx/access.log json;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*.conf;
}
```

```
sudo systemctl restart nginx
```

- bench 実行する

```
alp json --config ~/alp.yml --file /var/log/nginx/access.log
```

![image](https://hackmd.io/_uploads/HkAACVCQT.png)

### 静的ファイルのキャッシュ

- alpで遅いのを観測する


- nginxの設定

```
sudo vim /etc/nginx/sites-enabled/isucondition.conf
```

```
# 静的ファイルのディレクトリを指定
root /home/isucon/webapp/public;

location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
   expires 30d;
}
```

### nginxのworker_processを増やす

### request body周り

- /var/log/nginx/error.logを見ると以下のWARNログが出てる

クライアントからのリクエストボディが大きすぎてnginxがメモリ内に収められず、代わりに一時ファイルにバッファリングされている

```
[warn] 385673#385673: *302 a client request body is buffered to a temporary file /var/lib/nginx/body/0000000041, client: 163.43.208.17, server: , request: "POST /api/isu HTTP/2.0", host: "163.43.129.232:443"
```

```
[warn] 385673#385673: *118497 an upstream response is buffered to a temporary file /var/lib/nginx/proxy/6/09/0000000096 while reading 
upstream, client: 163.43.208.17, server: , request: "GET /api/isu/d6e9595b-379e-46fb-969e-a6cb9469802c/icon HTTP/2.0", upstream: "http://163.43.145.72:300
0/api/isu/d6e9595b-379e-46fb-969e-a6cb9469802c/icon", host: "163.43.129.232:443"
```

### MySQL も Web App もボトルネックに見えないとき

- MySQL のコネクション数を増やすと改善する場合がある
    - どんなとき: SQL でレコードの Lock が発生しているとき
    - コネクションは張っているが仕事はしていないので、もっと他のタスクをくれ状態になる
