ISUCON13を通して得た知識

# 概要

この記事では主に自分がISUCONの練習から本番を通して得た知識について書きました！主に[達人が教えるWebサイトパフォーマンスチューニング](https://gihyo.jp/book/2022/978-4-297-12846-3)（以下ISUCON本）という書籍から学んだことを抜粋しています。

## MySQLのEXPLAINについて

業務ではPostgreSQLを使用していることもあり、ISUCONの練習に取り組むまでMySQLのEXPLAINで出力される`Extra`の部分についてあまり理解できていませんでしたが、ISUCON本のおかげでExtraに出力されている意味、その後行うべき調整を一部知ることができました。

### ISUCON本に出てくる、インデックスの適応例

最初はインデックスが１つも貼られていない

```sql
mysql> EXPLAIN SELECT * FROM `comments` WHERE `post_id` = 10001 ORDER BY `created_at` DESC LIMIT 3;
+----+-------------+----------+------------+------+---------------+------+---------+------+-------+----------+-----------------------------+
| id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra                       |
+----+-------------+----------+------------+------+---------------+------+---------+------+-------+----------+-----------------------------+
|  1 | SIMPLE      | comments | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 99603 |    10.00 | Using where; Using filesort |
+----+-------------+----------+------------+------+---------------+------+---------+------+-------+----------+-----------------------------+
1 row in set, 1 warning (0.00 sec)
```

`post_id`にINDEX付与してみる

```sql
mysql> ALTER TABLE comments ADD INDEX post_id_idx(post_id);

mysql> EXPLAIN SELECT * FROM `comments` WHERE `post_id` = 10001 ORDER BY `created_at` DESC LIMIT 3;
+----+-------------+----------+------------+------+---------------+-------------+---------+-------+------+----------+----------------+
| id | select_type | table    | partitions | type | possible_keys | key         | key_len | ref   | rows | filtered | Extra          |
+----+-------------+----------+------------+------+---------------+-------------+---------+-------+------+----------+----------------+
|  1 | SIMPLE      | comments | NULL       | ref  | post_id_idx   | post_id_idx | 4       | const |    1 |   100.00 | Using filesort |
+----+-------------+----------+------------+------+---------------+-------------+---------+-------+------+----------+----------------+
1 row in set, 1 warning (0.01 sec)
```

これだけでも読み取る行数の減少（`rows`）など改善が見られますが、Extraには`Extra: Using filesort`が記載されています。

これはMySQL内部でソート処理が走っていることを表しているので、インデックスを`created_at`を含めた複合インデックスに変更します。

```sql
mysql> ALTER TABLE comments ADD INDEX post_id_idx(post_id, created_at);

mysql> EXPLAIN SELECT * FROM `comments` WHERE `post_id` = 10001 ORDER BY `created_at` DESC LIMIT 3;
+----+-------------+----------+------------+------+---------------+-------------+---------+-------+------+----------+---------------------+
| id | select_type | table    | partitions | type | possible_keys | key         | key_len | ref   | rows | filtered | Extra               |
+----+-------------+----------+------------+------+---------------+-------------+---------+-------+------+----------+---------------------+
|  1 | SIMPLE      | comments | NULL       | ref  | post_id_idx   | post_id_idx | 4       | const |    1 |   100.00 | Backward index scan |
+----+-------------+----------+------------+------+---------------+-------------+---------+-------+------+----------+---------------------+
1 row in set, 1 warning (0.01 sec)
```

Extraが変わり`Extra: Backward index scan`となっています。これは昇順に並んでいるインデックスを逆向きに読んだことを表しているので`created_at`を降順にするするようにインデックスを変更します。

```sql
mysql> ALTER TABLE comments ADD INDEX post_id_idx(post_id, created_at desc);

mysql> EXPLAIN SELECT * FROM `comments` WHERE `post_id` = 10001 ORDER BY `created_at` DESC LIMIT 3;
+----+-------------+----------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
| id | select_type | table    | partitions | type | possible_keys | key         | key_len | ref   | rows | filtered | Extra |
+----+-------------+----------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | comments | NULL       | ref  | post_id_idx   | post_id_idx | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+----------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.01 sec)
```

これによりExtraには何も表示されなくなりました。

Extraを見てインデックスを調整していく、ということをこれまで行ったことがなかったのでこうやって見るのか！と大きな学びとなりました。

## alp でアクセスログ解析

Nginxのログ出力形式をjsonに変更し、alpコマンドでアクセスログの解析を行いました。

```nginx
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;                            
```

```sh
$ alp json --sort sum -r -m "/posts/[0-9]+,/@\w+,/image/\d+" -o count,method,uri,min,avg,max,sum --file access.log

+-------+--------+----------------------+-------+-------+-------+----------+
| COUNT | METHOD |         URI          |  MIN  |  AVG  |  MAX  |   SUM    |
+-------+--------+----------------------+-------+-------+-------+----------+
| 8553  | GET    | /image/\d+           | 0.001 | 0.157 | 2.506 | 1345.022 |
| 718   | GET    | /                    | 0.175 | 0.965 | 5.842 | 692.765  |
| 169   | GET    | /@\w+                | 0.248 | 1.142 | 5.871 | 192.952  |
| 832   | GET    | /posts/[0-9]+        | 0.007 | 0.206 | 1.711 | 171.585  |
| 508   | POST   | /login               | 0.002 | 0.199 | 5.108 | 101.078  |
| 92    | GET    | /posts               | 0.171 | 0.996 | 4.503 | 91.627   |
| 154   | POST   | /                    | 0.003 | 0.313 | 1.230 | 48.186   |
| 103   | POST   | /register            | 0.012 | 0.188 | 1.492 | 19.415   |
| 103   | GET    | /admin/banned        | 0.002 | 0.171 | 2.101 | 17.620   |
| 94    | POST   | /comment             | 0.009 | 0.161 | 0.823 | 15.152   |
| 104   | GET    | /login               | 0.003 | 0.142 | 0.904 | 14.743   |
| 52    | GET    | /logout              | 0.001 | 0.162 | 0.701 | 8.417    |
| 5     | GET    | /initialize          | 0.061 | 1.195 | 5.303 | 5.976    |
| 998   | GET    | /js/timeago.min.js   | 0.000 | 0.000 | 0.000 | 0.000    |
| 998   | GET    | /js/main.js          | 0.000 | 0.000 | 0.000 | 0.000    |
| 998   | GET    | /css/style.css       | 0.000 | 0.000 | 0.000 | 0.000    |
| 997   | GET    | /favicon.ico         | 0.000 | 0.000 | 0.000 | 0.000    |
| 1     | GET    | /img/ajax-loader.gif | 0.000 | 0.000 | 0.000 | 0.000    |
+-------+--------+----------------------+-------+-------+-------+----------+
```

## dstat でシステムリソースの監視

dstatを以下のオプションを指定して実行し、CPU負荷などの監視を行っていました。

```sh
$ dstat -ctm -C 0,1,2,3,total
-----cpu0-usage----------cpu1-usage----------cpu2-usage----------cpu3-usage-------total-cpu-usage-- ----system---- ------memory-usage-----
usr sys idl wai stl:usr sys idl wai stl:usr sys idl wai stl:usr sys idl wai stl:usr sys idl wai stl|     time     | used  free  buff  cach
  9   2  89   0   0:  9   2  88   1   0:  5   2  93   0   0:  4   1  94   0   0:  7   2  91   0   0|16-12 10:06:17| 507M 5811M 44.7M 1423M
 96   2   2   0   0:  1   0  99   0   0:  2   6  92   0   0:  0   0 100   0   0: 24   2  74   0   0|16-12 10:06:18| 507M 5801M 44.7M 1432M
 96   3   1   0   0:  0   1  99   0   0:  1   4  95   0   0:  0   0 100   0   0: 24   2  74   0   0|16-12 10:06:19| 507M 5796M 44.7M 1438M
 ```

 ![describe_dstat.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533114/5d376a5c-cf9b-b731-76f6-6b3314f7cd35.png)

上記の例ではcpuコア数が4つだったので`-C 0,1,2,3,total`と指定しています。
cpuコア数は以下のコマンドで確認しました。

 ```sh
$ lscpu
Architecture:    x86_64
CPU op-mode(s):  32-bit, 64-bit
Byte Order:      Little Endian
Address sizes:   46 bits physical, 48 bits virtual
CPU(s):          4 # ←これ
~
 ```

## New Relic の導入

ISUCONのスポンサーでもあり、導入記事が充実していたことから練習、本番で利用しました。

導入方法の記事をただ実行するだけですぐにデータを確認することができました。

https://newrelic.com/jp/blog/how-to-relic/isucon-infra

上記に加えて、Rubyのエージェントも導入して利用していました。

https://docs.newrelic.com/jp/docs/apm/agents/ruby-agent/installation/install-new-relic-ruby-agent/

こちらも公式の手順ドキュメントを見るだけでつまずくことなく導入できました。

上記までの設定によりCPU利用率の確認、どのAPIで負荷がかかっているかなどをブラウザで確認していました。

![スクリーンショット 2023-11-24 0.03.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533114/391712bb-17ca-553f-dbc2-cd0309a6b741.png)

![スクリーンショット 2023-11-24 0.01.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533114/17cf4415-8e77-898c-de2e-8e48dfd0406b.png)

## 終わりに

ISUCONに参加してみて、ボトルネックを見つけて改善することでスコアに反映されるという体験がまずおもしろいと感じました。

ISUCONで行うボトルネック改善などは、個人で勉強するには腰が重いなと感じていたのですが、ISUCONに参加することで「やらなきゃ！」となるので、それにより様々な対策を知れる＆実装できるのはよい機会でした。
なによりISUCONはサーバー、ベンチマーカー、改善余地があるアプリケーションなどを用意してくださることが１番ありがたいなと感じました。

今後は引き続きISUCON本の問題、その他過去問に取り組んで来年に備えたいなと考えています！