ISUCONで学ぶGo private-isu環境構築編 alp,pprof,pt-query-digestの導入方法

# IsuconでGoの勉強をしてみる
今回初めてisuconに出ることになり、色々調べてみるとほとんどのチームがGoを使うことがわかった。初心者としては情報が多い方が良さそうなので使ったことはないがGoで挑戦することを決意。
どうやってGoを学ぼうか迷っていたが、Goの書き方やら環境構築やらを行うのがめんどうなので、いっそのことisuconの過去問を眺めながらGoについて学ぶことにした。
# 筆者のスペック
- 大学生
- バックエンドを中心に一年位インターン
- **Go未経験**
- Isucon未経験

# 環境構築
今回導入するツールは
- alp(nginxログ監視ツール)
- pt-query-digest
- pprof

まずは環境構築や、問題を解くためのツールの導入などをおこなう。
以下の記事を参考にしてmultipassで環境構築を行なった。
今回解くのはisuconの練習問題であるprivate-isu

https://github.com/catatsuy/private-isu/blob/master/manual.md

今回はgoで練習したいのでマニュアルを参考にして以下のコマンドを実行

```bash
$ sudo systemctl stop isu-ruby
$ sudo systemctl disable isu-ruby
$ sudo systemctl start isu-go
$ sudo systemctl enable isu-go
```
multipassでIPアドレスを調べて、```https://{IPアドレス}```にアクセスし、以下のようなサイトが表示されれば成功

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662112/f3268300-efec-9223-1e44-080cae41d085.png)

ベンチマークを回してみる。
```
{"pass":true,"score":4055,"success":3914,"fail":0,"messages":[]}
```
問題なく実行できた

## 各種ツールの導入
まず初めにvscodeからアクセスできるようにsshキー登録する。
まずローカル側で鍵の作成。
```bash
cd ~/.ssh
ssh-keygen -t rsa -f id_rsa_isu
```
公開鍵をコピー
```bash
cat id_rsa_isu.pub
```
multipassインスタンスに~/.ssh/authorized_keysを作成し、先ほどのコピーを追加
```bash
cd ~/.ssh
touch authorized_keys
chmod 600 authorized_keys
vi authorized_keys
```
ローカルから接続できることを確認
```bash
ssh isucon@***.***.***.*** -i ~/.ssh/id_rsa_isu
```
あとはvscodeからリモート接続できれば完了

## alp
alpが一番有名そうなので今回はこちらを使用してみる。
以下のコマンドでダウンロード
```bash
wget https://github.com/tkuchiki/alp/releases/download/v1.0.9/alp_linux_arm64.tar.gz
tar -zxvf alp_linux_arm64.tar.gz
sudo install alp /usr/local/bin/alp
```
その後、以下を実行して動作確認
```bash
alp --version
```
次にnginxでログを出力するように設定
```bash
sudo vi /etc/nginx/nginx.conf
```
以下の設定をhttpの中に追加
```vim
        ##
          log_format ltsv "time:$time_local"
            "\thost:$remote_addr"
            "\tforwardedfor:$http_x_forwarded_for"
            "\treq:$request"
            "\tmethod:$request_method"
            "\turi:$request_uri"
            "\tstatus:$status"
            "\tsize:$body_bytes_sent"
            "\treferer:$http_referer"
            "\tua:$http_user_agent"
            "\treqtime:$request_time"
            "\truntime:$upstream_http_x_runtime"
            "\tapptime:$upstream_response_time"
            "\tcache:$upstream_http_x_cache"
            "\tvhost:$host";
        access_log  /var/log/nginx/access.log  ltsv;
```
## pt-query-digest
次はsqlのログを取得するためのツールを導入
/etc/mysql/conf.d/my.cnfでmysqlの設定が行えるらしいがなぜかそのファイル自体がなかったので以下のコマンドで探してみる
```bash
isucon@private-isu:~$ sudo find / -name my.cnf
find: ‘/proc/89099’: No such file or directory
find: ‘/proc/89100’: No such file or directory
find: ‘/proc/89101’: No such file or directory
/tmp/private-isu/ansible_old/roles/portal/files/mysql/my.cnf
/var/lib/dpkg/alternatives/my.cnf
/etc/alternatives/my.cnf
/etc/mysql/my.cnf
/home/isucon/private_isu.git/ansible_old/roles/portal/files/mysql/my.cnf
```
/etc/mysql/my.cnfにあるらしいので開いてみる
```bash
sudo vi /etc/mysql/my.cnf
```
```vim
#
# The MySQL database server configuration file.
#
# You can copy this to one of:
# - "/etc/mysql/my.cnf" to set global options,
# - "~/.my.cnf" to set user-specific options.
# 
# One can use all long options that the program supports.
# Run program with --help to get a list of available options and with
# --print-defaults to see which it would actually understand and use.
#
# For explanations see
# http://dev.mysql.com/doc/mysql/en/server-system-variables.html

#
# * IMPORTANT: Additional settings that can override those from this file!
#   The files must end with '.cnf', otherwise they'll be ignored.
#

!includedir /etc/mysql/conf.d/
!includedir /etc/mysql/mysql.conf.d/
```
この内容をみるとMySQLの設定は/etc/mysql/conf.d/や/etc/mysql/mysql.conf.d/ディレクトリ内の.cnfファイルによって追加または上書きできるようになっている。
よって、以下のように新しくファイルを作成して設定を書き込む。
```bash
sudo vi /etc/mysql/conf.d/custom-my.cnf
```
```vim
[mysqld]
character-set-server=utf8mb4
slow_query_log=ON
long_query_time=0.001
slow_query_log_file=/var/log/mysql/my-slow.log

[mysql]
default-character-set=utf8mb4

[client]
default-character-set=utf8mb4
```
ログを出力するファイルに書き込み権限を与える
```bash
sudo mkdir -p /var/log/mysql
sudo chown mysql:mysql /var/log/mysql
```
再起動
```
sudo systemctl restart mysql
```
その後percona-toolkitをインストール(この中にpt-query-digestが含まれる)
```bash
sudo apt install -y percona-toolkit
```
その後にbenchを回した後に以下のコマンドを実行
```bash
sudo pt-query-digest /var/log/mysql/my-slow.log > ~/pt-query-digest.txt
```
以下のようなファイルが作成されれば成功
```text
# 1.8s user time, 390ms system time, 40.54M rss, 47.60M vsz
# Current date: Sat Oct 12 00:48:34 2024
# Hostname: private-isu
# Files: /var/log/mysql/my-slow.log
# Overall: 25.73k total, 27 unique, 0.55 QPS, 0.04x concurrency __________
# Time range: 2024-10-11T02:32:18 to 2024-10-11T15:36:18
# Attribute          total     min     max     avg     95%  stddev  median
# ============     ======= ======= ======= ======= ======= ======= =======
# Exec time          1886s     1ms      1s    73ms   155ms    51ms    56ms
# Lock time           82ms       0    13ms     3us     1us   129us       0
# Rows sent          3.96M       0   9.77k  161.26    6.98   1.17k    0.99
# Rows examine       2.07G       0  97.68k  84.50k  97.04k  32.68k  97.04k
# Query size        14.46M      10   1.17M  589.02   80.10  21.26k   65.89

# Profile
# Rank Query ID                      Response time   Calls R/Call V/M   It
# ==== ============================= =============== ===== ====== ===== ==
#    1 0x624863D30DAC59FA16849282... 1192.1396 63.2% 10026 0.1189  0.01 SELECT comments
#    2 0x422390B42D4DD86C7539A5F4...  516.1626 27.4% 10960 0.0471  0.01 SELECT comments
#    3 0x100EC8B5C400F34381F9D7F7...  116.6155  6.2%   934 0.1249  0.01 SELECT comments
# MISC 0xMISC                          61.2725  3.2%  3815 0.0161   0.0 <24 ITEMS>

# Query 1: 0.21 QPS, 0.03x concurrency, ID 0x624863D30DAC59FA16849282195BE09F at byte 4566541
# This item is included in the report because it matches --limit.
...
```
## pprofの導入

### 手順概要

1. **Goアプリケーションに`pprof`を組み込む**
2. **`pprof`サーバーを有効にする**
3. **プロファイルデータを取得**
4. **ローカルでプロファイルデータを分析**
5. **グラフ化して視覚的に分析**

---

### 1. **Goアプリケーションに`pprof`を組み込む**

Goアプリケーションで`pprof`を利用するには、`net/http/pprof`パッケージをインポートして、プロファイリング用のエンドポイントを有効にする必要があります。`main.go`やアプリケーションのエントリーポイントで以下のようにコードを追加します。

#### 例: `pprof`のインポートと設定

```go
import (
    _ "net/http/pprof"
    "log"
    "net/http"
)

func main() {
    // pprof用のHTTPサーバーを起動 (ポート6060)
    go func() {
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()

    // アプリケーションの他のコード
}
```

- この設定により、`http://localhost:6060/debug/pprof/`にアクセスすることで、`pprof`のプロファイリング機能が利用可能になります。
- `go func()`内で別ゴルーチンを使って`pprof`サーバーをバックグラウンドで起動しています。
- Go初心者ならではかもしれないが、goはコンパイル言語なので、以下のコマンドでbuildしないと**変更が反映されないことに注意**(アプリの再起動も)
```
go build -o app
sudo systemctl stop isu-go
sudo systemctl restart isu-go
```
### 2. **`pprof`サーバーを有効にする**

`pprof`が正しく起動しているか確認します。アプリケーションが実行中のサーバーにアクセスし、`pprof`のエンドポイントが利用可能かどうかを確認します。

```bash
curl http://localhost:6060/debug/pprof/
```

- 正常に動作している場合、`pprof`のエンドポイントの一覧が表示される。

### 3. **プロファイルデータを取得**

`pprof`のエンドポイントからプロファイルデータを収集します。たとえば、CPUプロファイルを60秒間収集する場合、次のコマンドを実行。

#### CPUプロファイルの取得例

```bash
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=60
```

このコマンドを実行すると、60秒間のプロファイルデータが収集され、`pprof`のインタラクティブな解析環境が起動します。

### 4. **ローカルでプロファイルデータを分析**

取得したプロファイルデータをローカルでさらに分析することも可能です。プロファイルデータをファイルとして保存し、後から解析できるらしい。

#### プロファイルデータの保存と解析

```bash
# プロファイルデータを保存
curl -o profile.pb.gz http://localhost:6060/debug/pprof/profile?seconds=60

# 保存したプロファイルデータを解析
go tool pprof profile.pb.gz
```

これにより、`pprof`の解析インターフェースが起動し、データを対話的に分析できる。
ただ、このprofile.pb.gzがあれば解析はできるので、以下ではこのファイルをローカルに落としてweb上で解析してみる

### 5. **グラフ化して視覚的に分析**

`pprof`の解析インターフェースで`Graphviz`を利用して、プロファイルデータを視覚的にグラフ化できる。まず、`Graphviz`をインストール。

#### `Graphviz`のインストール（macOSの場合）

```bash
brew install graphviz
```

#### グラフの表示

`pprof`のインターフェース内で以下のコマンドを実行して、グラフをWebブラウザで表示。

```bash
go tool pprof profile.pb.gz
(pprof) web
```

これにより、ブラウザでグラフ化されたプロファイルデータを見ることができる。また、SVGやPNG形式でグラフを生成することも可能

成功すると以下の様なサイトが開く
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662112/ddd127e1-47db-166b-9993-21ee4b05d7b6.png)

# まとめ
次回はこの環境を用いてGoの勉強をしながらパフォーマンス改善を行う