達人が教えるWebパフォーマンスチューニング

カバー画像

達人が教えるWebパフォーマンスチューニング〜ISUCONから学ぶ高速化の実践 を読んだ覚え書き。 private-isuを題材として手を動かしつつWebアプリのパフォーマンスチューニングを学ぶことができる。

モチベーション

  • パフォーマンスチューニングを興味がある
  • Grafana Cloudを使ってみたい

システム構成

  • Arch Linux (Hypervisor)
    • private-isu app実行環境 (VM 4vCPU, 7.5GB RAM)
    • private-isu benchmerker実行環境 (VM 4vCPU, 7.5GB RAM)
  • Mac
    • vscodeクライアント
  • Grafana Cloud
    • Alloy
    • Grafana

private-isu環境は private-isu のVagrantを参考にして構築する。

private-isu app実行環境の構築

自宅サーバのKVM上で動かせるようにVagrantファイルに変更を入れる。

show diff
❯ git diff Vagrantfile
diff --git a/Vagrantfile b/Vagrantfile
index da33de2..667d343 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -1,7 +1,8 @@
 # vi: set ft=ruby :

 Vagrant.configure("2") do |config|
-  config.vm.box = "ubuntu/jammy64"
+  #config.vm.box = "ubuntu/jammy64"
+  config.vm.box = "bento/ubuntu-22.04"
   config.vm.box_check_update = false

   config.vm.network "private_network", type: "dhcp"
@@ -9,9 +10,12 @@ Vagrant.configure("2") do |config|
   config.vm.define "app" do |app|
     app.vm.hostname = "app"
     app.vm.network "forwarded_port", guest: 80, host: 8000
-    app.vm.provider "virtualbox" do |vb|
-      vb.cpus = 2
-      vb.memory = 1500
+    #app.vm.provider "virtualbox" do |vb|
+    app.vm.provider "libvirt" do |vb|
+      vb.cpus = 4
+      vb.memory = 7680
+      # vb.cpus = 2
+      # vb.memory = 1500
     end

     app.vm.provision "ansible" do |ansible|
@@ -29,7 +33,7 @@ Vagrant.configure("2") do |config|

   config.vm.define "bench" do |bench|
     bench.vm.hostname = "bench"
-    bench.vm.provider "virtualbox" do |vb|
+    bench.vm.provider "libvirt" do |vb|
       vb.cpus = 4
       vb.memory = 7680
     end

AnsibleではRuby, PHPの実行環境の構築に失敗してしまう。
使う予定もないので下記ファイルから該当の処理をコメントアウトする。

  • provisioning/image/ansible/03_nginx.yml
  • provisioning/image/ansible/04_xbuild.yml
  • provisioning/image/ansible/07_application.yml

VagrantでVMを立ち上げ、private-isu の manual.md を参考にgo実装に切り替える。

app.local でアクセスできるようにするために avahi をインストールする。

sudo apt install -y avahi-daemon

private-isu benchmerker実行環境

同じくVagrantでVMを立ち上げ、imageデータをダウンロード・展開する。

cd private-isu-bench/benchmarker
make

cd private-isu-bench/benchmarker/userdata
curl -# -OL https://github.com/catatsuy/private-isu/releases/download/img/img.zip
unzip img.zip

bench.local でアクセスできるようにするために avahi をインストールする。

sudo apt install -y avahi-daemon

benchmarkを走らせて動作確認。

/home/isucon/private_isu/benchmarker/bin/benchmarker -u /home/isucon/private_isu/benchmarker/userdata -t http://app.local

どうやらScore:0、 エラーありの状態から開始らしい。
そんなポンコツスペックではないのでなにかおかしそうだが、気にせず進める。

{"pass":false,"score":0,"success":4,"fail":6,"messages":["リクエストがタイムアウトしました (POST /login)","リクエストがタイムアウトしました (POST /register)"]}

監視環境を作る

process-exporterの導入

topコマンドみたいなプロセスごとのCPU Usageが見たい。
app.localにprocess-exporterを入れる。

wget https://github.com/ncabatoff/process-exporter/releases/download/v0.8.7/process-exporter_0.8.7_linux_amd64.deb

sudo apt install ./process-exporter_0.8.7_linux_amd64.deb
rm ./process-exporter_0.8.7_linux_amd64.deb

Alloyでメトリクス転送

install grafana alloy on linux を参考に app.local にAlloyをインストールする。

app.local 環境にインストールしたalloyのconfig: /etc/alloy/config.alloy

prometheus.scrape "local_exporter" {
  targets = [
    {"__address__" = "localhost:9256", "instance" = "private-isu"},
  ]
  forward_to = [prometheus.remote_write.grafana_cloud.receiver]
}

prometheus.remote_write "grafana_cloud" {
  endpoint {
    url = "https://prometheus-prod-xxx.grafana.net/api/prom/push"

	basic_auth {
        username = "xxx"
        password = "xxx"
      }
  }
}

alloy をリスタート

sudo systemctl enable alloy
sudo systemctl start alloy
systemctl status process-exporter

grafanaで可視化

Grafana

CPU Usage

(sum(irate(namedprocess_namegroup_cpu_seconds_total{instance="private-isu", groupname=~"app|alloy|mysqld|nginx|node|code-848b80aeb5"}[$__rate_interval])) by (groupname) > 0) / scalar(count(count(node_cpu_seconds_total) by (cpu)))

File Descriptor Usage

namedprocess_namegroup_open_filedesc{instance=~"private-isu", job="$job"}

Memory Usage

namedprocess_namegroup_memory_bytes{instance="private-isu", memtype="resident", job="$job"} / ignoring(memtype) namedprocess_namegroup_num_procs > 0

Threads (これはあまり見なかった)

namedprocess_namegroup_num_threads{instance="private-isu", job="$job"}

プロファイラ

alp

nginxのアクセスログを解析するツール。

インストール。

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

使用できるか確認

$ alp -v
1.0.21

pt-query-digest

MySQLのスロークエリを解析するツール。

インストール。 ref. GitHub

cd /tmp
git clone https://github.com/percona/percona-toolkit.git
cd percona-toolkit

perl Makefile.PL
make
make test
make install # /usr/local/binへのwrite権限がなく失敗する
sudo cp -a /tmp/percona-toolkit/blib/script/pt-query-digest /usr/local/bin/

pt-query-digestでスロークエリを解析。

pt-query-digest /var/log/mysql/mysql-slow.log

パフォーマンスチューニングのログ

初期状態

Score

{"pass":false,"score":0,"success":4,"fail":6,"messages":["リクエストがタイムアウトしました (POST /login)","リクエストがタイムアウトしました (POST /register)"]}

commentsテーブルの post_id, created_at にインデックスを貼る

ALTER TABLE comments ADD INDEX post_id_idx (post_id, created_at DESC);

Score
スコアが一気に伸びた。

{"pass":true,"score":1947,"success":1853,"fail":0,"messages":[]}

静的ファイルをnginxで配信 # Commit 742d6bc

favicon.ico, css, js, img をnginxから配信する。

$ git diff etc/nginx.conf
-  root /home/isucon/private_isu/webapp/public/;
+  root /home/isucon/private_isu/webapp/public;
+
+  location ~^/(favicon\.ico|css/|js/|img/) {
+    root /home/isucon/private_isu/webapp/public/;
+    expires 1;
+  }

Score
6,000 くらい向上した。

{"pass":true,"score":2107,"success":2011,"fail":0,"messages":[]}

アップロード画像を静的ファイルとして配信 # Commit c61e4f8

App → MySQLへ問い合わせると、MySQLから取得した画像のバイナリデータを一度メモリに載せてからレスポンスを返すため、オーバーヘッドが大きい。
アプリケーションを介さず静的ファイルとして配信する。

Score

{"pass":true,"score":3020,"success":2901,"fail":0,"messages":[]}

GET / を改修する # Commit 7ccdcdb

GET / のmakePosts()の中で、post1件毎にusersテーブルにクエリを発行するN+1問題が発生している。postsテーブルとusersテーブルをJOINして読み込む行数を減らすことを試みる。

mysql> SELECT COUNT(*) FROM `posts`;
+----------+
| COUNT(*) |
+----------+
|    10183 |
+----------+
1 row in set (0.03 sec)

mysql> EXPLAIN SELECT p.id, p.user_id, p.body, p.mime, p.created_at, u.account_name FROM posts AS p JOIN users AS u ON (p.user_id=u.id) WHERE u.del_flg=0 ORDER BY p.created_at DESC LIMIT 20\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: p
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 10128
     filtered: 100.00
        Extra: Using filesort
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: u
   partitions: NULL
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: isuconp.p.user_id
         rows: 1
     filtered: 10.00
        Extra: Using where
2 rows in set, 1 warning (0.00 sec)

postsテーブルからほぼ全行の読み込みをしている。インデックスを貼って読み込む行数を減らす。

ALTER TABLE posts ADD INDEX posts_order_idx (created_at DESC);
mysql> EXPLAIN SELECT p.id, p.user_id, p.body, p.mime, p.created_at, u.account_name FROM posts AS p JOIN users AS u ON (p.user_id=u.id) WHERE u.del_flg=0 ORDER BY p.created_at DESC LIMIT 20\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: p
   partitions: NULL
         type: index
possible_keys: NULL
          key: posts_order_idx
      key_len: 4
          ref: NULL
         rows: 199
     filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: u
   partitions: NULL
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: isuconp.p.user_id
         rows: 1
     filtered: 10.00
        Extra: Using where
2 rows in set, 1 warning (0.00 sec)

良さそう。この結果を持ってapp.goを改修する。
sqlxではJOINをそのまま使えないらしい。user構造体を持つpost構造体に、user構造体の値 u.account_nameをdb.Select()から入れようとするとエラーになる。

type User struct {
	ID int `db:"id"`
	AccountName string `db:"account_name"`
	Passhash string `db:"passhash"`
	Authority int `db:"authority"`
	DelFlg int `db:"del_flg"`
	CreatedAt time.Time `db:"created_at"`
}
type Post struct {
	ID int `db:"id"`
	UserID int `db:"user_id"`
	Imgdata []byte `db:"imgdata"`
	Body string `db:"body"`
	Mime string `db:"mime"`
	CreatedAt time.Time `db:"created_at"`
	CommentCount int
	Comments []Comment
	User User
	CSRFToken string
}

func getIndex(w http.ResponseWriter, r *http.Request) {
	results := []Post{}
	  
	err := db.Select(&results, "SELECT p.id, p.user_id, p.body, p.mime, p.created_at, u.account_name FROM posts AS p JOIN users AS u ON (p.user_id=u.id) WHERE u.del_flg=0 ORDER BY p.created_at DESC LIMIT ?", postsPerPage)
...
}
Jun 01 06:37:17 private-isu app[54238]: 2025/06/01 06:37:17 app.go:392: missing destination name account_name in *[]main.Post

以下のように明示的にuser構造体のフィールドであることを指定して解消できた。

u.account_name as `user.account_name`

Score

{"pass":true,"score":4424,"success":4242,"fail":0,"messages":[]}

プリペアドステートメントの改修 # Commit 8b72a8d

interpolateParams=true をdsnに追加すると良いらしい。
prepare と interpolateParams=true | tetsuzawa.com

Score

{"pass":true,"score":6104,"success":5870,"fail":0,"messages":[]}

comments テーブルへインデックスを追加する

slow-query

# Profile
# Rank Query ID                      Response time Calls R/Call V/M   Item
# ==== ============================= ============= ===== ====== ===== ====
#    1 0x396201721CD58410E070DA94... 77.9625 32.4% 13906 0.0056  0.01 SELECT users
#    2 0x624863D30DAC59FA16849282... 40.9843 17.0%  5120 0.0080  0.01 SELECT comments
#    3 0x422390B42D4DD86C7539A5F4... 36.4387 15.1%  5303 0.0069  0.01 SELECT comments
#    4 0xCDEB1AFF2AE2BE51B2ED5CF0... 29.5805 12.3%    30 0.9860  0.05 SELECT comments

1,2,3は呼び出し回数(Call)が多いが、1呼び出しあたりの処理時間(R/Call)は短いためアプリ側の改善が必要にみえる。 4番目のクエリは呼び出し回数が少ないが1呼び出しあたりの処理時間が長いため改善の余地がある。対象のSQLは以下。

mysql> EXPLAIN SELECT COUNT(*) AS count FROM `comments` WHERE `user_id` = 724\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: comments
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 99359
     filtered: 10.00
        Extra: Using where
1 row in set, 1 warning (0.02 sec)

ほぼ全探索してしまっている。user_idにインデックスを貼る。

ALTER TABLE `comments` ADD INDEX `idx_user_id` (`user_id`);

Score

{"pass":true,"score":6434,"success":6195,"fail":0,"messages":[]}

N+1問題の解消 # Commit 40cef4d

# Profile
# Rank Query ID                      Response time Calls R/Call V/M   Item
# ==== ============================= ============= ===== ====== ===== ====
#    1 0x396201721CD58410E070DA94... 82.6631 36.8% 14687 0.0056  0.01 SELECT users
#    2 0x624863D30DAC59FA16849282... 42.7369 19.0%  5284 0.0081  0.01 SELECT comments
#    3 0x422390B42D4DD86C7539A5F4... 39.4725 17.6%  5492 0.0072  0.01 SELECT comments

---
SELECT * FROM `users` WHERE `id` = 360\G
SELECT * FROM `comments` WHERE `post_id` = 9963 ORDER BY `created_at` DESC LIMIT 3\G
SELECT COUNT(*) AS `count` FROM `comments` WHERE `post_id` = 9995\G

makePosts() 関数のloopでDBへの問い合わせが頻発している。

for _, p := range results {
	err := db.Get(&p.CommentCount, "SELECT COUNT(*) AS `count` FROM `comments` WHERE `post_id` = ?", p.ID)
	if err != nil {
		return nil, err
	}

	query := "SELECT * FROM `comments` WHERE `post_id` = ? ORDER BY `created_at` DESC"
	if !allComments {
		query += " LIMIT 3"
	}
	var comments []Comment
	err = db.Select(&comments, query, p.ID)
	if err != nil {
		return nil, err
	}

	for i := 0; i < len(comments); i++ {
		err := db.Get(&comments[i].User, "SELECT * FROM `users` WHERE `id` = ?", comments[i].UserID)
		if err != nil {
			return nil, err
		}
	}

まずはJOINを使ってN+1問題を解消する。

Score

2,000 上昇した。効果はかなり大きそう。

{"pass":true,"score":8589,"success":8306,"fail":0,"messages":[]}

memcachedによるコメント関連のキャッシュ # Commit ee0e5bd

memcachedを使ってキャッシュする。仕様は以下。

  • makePosts関数の中でmemcachedに問い合わせ
    • キャッシュがあれば使う
    • キャッシュがなければMySQLに問い合わせキャッシュを作成する
  • 投稿にコメントが追加された場合は、コメントのあった投稿に関するキャッシュを破棄する
  • TTLは10秒
  • 投稿ごとのコメント数のキーは comments.{posts.id}.count
  • 投稿ごとのコメント全件のキーは comments.{posts.id}.true
  • 投稿ごとのコメント最新3件のキーは comments.{posts.id}.false

Score

500向上。思ったより控えめ

{"pass":true,"score":9025,"success":8721,"fail":0,"messages":[]}

外部コマンドの呼び出しを辞める # Commit 45c868c

func digest(src string) string {Add commentMore actions
	return fmt.Sprintf("%x", sha512.Sum512([]byte(src)))

Score
ついに大台の1万を超えた。

{"pass":true,"score":10779,"success":10330,"fail":0,"messages":[]}

MySQLの設定調整でINSERTを高速にする # Commit 6a29538

5, 11, 12, 14, 16のINSERTを消し去りたい。

# Profile
# Rank Query ID                       Response time Calls R/Call V/M   Ite
# ==== ============================== ============= ===== ====== ===== ===
#    1 0x1FD3A7D53ECF44C9E39DB9A71... 17.0185 22.0%  1525 0.0112  0.01 SELECT comments users
#    2 0x422390B42D4DD86C7539A5F45... 11.2074 14.5%  1458 0.0077  0.01 SELECT comments
#    3 0xA047A0D0BA167343E5B367867...  7.2239  9.3%   235 0.0307  0.01 SELECT users
#    4 0x9F7A2CFF4B8BA81A450918E7E...  7.1677  9.3%   272 0.0264  0.02 SELECT posts
#    5 0x2822B8A98D614ECBDD1C1E56F...  6.0068  7.8%    15 0.4005  0.30 INSERT posts
#    6 0x312E0027ABA918FBCF55E5D19...  5.2580  6.8%   369 0.0142  0.01 SELECT posts users
#    7 0x19759A5557089FD5B718D440C...  5.1002  6.6%   137 0.0372  0.02 SELECT posts
#    8 0x42C29FB96699C9FDDB09D3D3F...  4.2100  5.4%   272 0.0155  0.01 SELECT comments users
#    9 0x396201721CD58410E070DA942...  2.0887  2.7%   316 0.0066  0.01 SELECT users
#   10 0x995F41A1456C1CF6746D96521...  1.9253  2.5%   929 0.0021  0.00 SET
#   11 0x26489ECBE26887E480CA8067F...  1.5483  2.0%    31 0.0499  0.04 INSERT users
#   12 0x16CFE2667473FE8863B8066E0...  1.4166  1.8%     2 0.7083  0.01 INSERT posts
#   13 0x82E4B026FA27240AB4BB2E774...  1.1999  1.5%    44 0.0273  0.02 SELECT users
#   14 0x9F2038550F51B0A3AB05CA526...  1.0381  1.3%    21 0.0494  0.01 INSERT comments
#   15 0x979CA34F096654135383AD4AD...  0.7422  1.0%    44 0.0169  0.01 SELECT posts
#   16 0x174B1FAB84BE4F47C2D96EA0B...  0.7392  1.0%     1 0.7392  0.00 INSERT posts

mysql.cnfの [mysqld] スタンザに設定を変更する。

[mysqld]
# コミットごとに更新データをログに書き、1秒毎にログをフラッシュ
innodb_flush_log_at_trx_commit = 2
# バイナリログを無効化する
disable-log-bin = 1

slow-query postsへのINSERTは残ってしまったが、その他のINSERTは姿を消した。

# Profile
# Rank Query ID                       Response time Calls R/Call V/M   Ite
# ==== ============================== ============= ===== ====== ===== ===
#    1 0x1FD3A7D53ECF44C9E39DB9A71... 14.7391 21.3%  1536 0.0096  0.01 SELECT comments users
#    2 0x422390B42D4DD86C7539A5F45... 10.3143 14.9%  1487 0.0069  0.01 SELECT comments
#    3 0x9F7A2CFF4B8BA81A450918E7E...  8.9774 13.0%   314 0.0286  0.04 SELECT posts
#    4 0xA047A0D0BA167343E5B367867...  6.6693  9.6%   255 0.0262  0.02 SELECT users
#    5 0x2822B8A98D614ECBDD1C1E56F...  5.6779  8.2%    20 0.2839  0.19 INSERT posts
#    6 0x312E0027ABA918FBCF55E5D19...  5.5711  8.1%   387 0.0144  0.02 SELECT posts users
#    7 0x42C29FB96699C9FDDB09D3D3F...  4.3243  6.3%   313 0.0138  0.01 SELECT comments users
#    8 0x19759A5557089FD5B718D440C...  2.9153  4.2%    99 0.0294  0.02 SELECT posts
#    9 0x396201721CD58410E070DA942...  1.9488  2.8%   344 0.0057  0.01 SELECT users
#   10 0x995F41A1456C1CF6746D96521...  1.8903  2.7%   997 0.0019  0.00 SET
#   11 0x82E4B026FA27240AB4BB2E774...  1.1899  1.7%    49 0.0243  0.01 SELECT users
#   12 0x979CA34F096654135383AD4AD...  0.7460  1.1%    49 0.0152  0.03 SELECT posts
#   13 0xC37F2207FE2E699A3A976F5EB...  0.7283  1.1%    49 0.0149  0.02 SELECT comments

---
INSERT INTO `posts` (`user_id`, `mime`, `imgdata`, `body`) VALUES (340,'image/png',_binary'�PNG\r\n\Z\n\0\...');

Score
400ほど向上した。

{"pass":true,"score":11101,"success":10630,"fail":0,"messages":[]}

memcachedへの問い合わせ回数を減らす # Commit e0bec0e

memcacheClient.Get() で都度問い合わせをしている箇所を、memcacheClient.GetMulti()を使ってまとめて問い合わせるようにする。

Score
2,000向上した。getMulti()の効果すごい。

{"pass":true,"score":13561,"success":12983,"fail":0,"messages":[]}

ここまでで書籍の内容は一通り終わり


pprofを導入する # Commit cd10305

pprofを動かしている状態でベンチマークを実行して基準となるScore, alp, slow-queryを更新する。

go tool pprof -seconds 70 -http=localhost:1080 http://localhost:6060/debug/pprof/profile

Score

{"pass":true,"score":12296,"success":11786,"fail":0,"messages":[]}

GoのTemplateを高速にする # Commit 4118f9a

pporfの結果を見るとtemplateの処理に結構な時間がかかっている。

getIndex() のtemplate.executeしている部分をfmt.Sprintf()に置き換えた。先人の力を借りる。
GET /, GET /admin/bannedでhtml/templateを使わないようにした

pprofの結果としてもだいぶ改善されている。

Score
4,000あがった。

{"pass":true,"score":16043,"success":15407,"fail":0,"messages":[]}

json.Marshal(), json.Unmsharshal() を高速にする

pprofの結果から以下の処理で時間かかっているように見える。

  • json.Unmarshal
  • json.Marshal
  • memcached

Marshal, Unmarshalが高速と噂のgo-jsonに置き換える。
https://pkg.go.dev/github.com/goccy/go-json

pprof上ではmarshal, unmarshalの所要時間が減ったが、Scoreはほとんど変化なし

Score

{"pass":true,"score":16002,"success":15353,"fail":0,"messages":[]}

account_nameにインデックスを貼る

ALTER TABLE users ADD INDEX users_accountname_idx(account_name);

Score
微増した。

{"pass":true,"score":16269,"success":15606,"fail":0,"messages":[]}

Scoreの推移

// 初期スコア
{"pass":false,"score":0,"success":4,"fail":6,"messages":["リクエストがタイムアウトしました (POST /login)","リクエストがタイムアウトしました (POST /register)"]}

// commentsテーブルの post_id, created_at にインデックスを貼る
{"pass":true,"score":1947,"success":1853,"fail":0,"messages":[]}

// 静的ファイルをnginxで配信 [# Commit 742d6bc](https://github.com/csnagu/private-isu-2025/commit/742d6bcfb32c000b0457f1f1c4e03900fac03859)
{"pass":true,"score":2107,"success":2011,"fail":0,"messages":[]}

// アップロード画像を静的ファイルとして配信 [# Commit c61e4f8](https://github.com/csnagu/private-isu-2025/commit/c61e4f83863962c2e7d2838ac6d5fa7957b0e59a)
{"pass":true,"score":3020,"success":2901,"fail":0,"messages":[]}

// GET / を改修する [# Commit 7ccdcdb](https://github.com/csnagu/private-isu-2025/commit/7ccdcdbd1aba267fd7c6605af8c644d9b9a99b6e)
{"pass":true,"score":4424,"success":4242,"fail":0,"messages":[]}

// プリペアドステートメントの改修 [# Commit 8b72a8d](https://github.com/csnagu/private-isu-2025/commit/8b72a8df42665ff5dcfccd5bf7bec229517479bb)
{"pass":true,"score":6104,"success":5870,"fail":0,"messages":[]}

// comments テーブルへインデックスを追加する
{"pass":true,"score":6434,"success":6195,"fail":0,"messages":[]}

// N+1問題の解消   [# Commit 40cef4d](https://github.com/csnagu/private-isu-2025/commit/40cef4df64a8825f775e23c74ce6e551318ce652)
{"pass":true,"score":8589,"success":8306,"fail":0,"messages":[]}

// memcachedによるコメント関連のキャッシュ  [# Commit ee0e5bd](https://github.com/csnagu/private-isu-2025/commit/ee0e5bdeedd32ffbd019b08461ab1e27d7ddb797)
{"pass":true,"score":9025,"success":8721,"fail":0,"messages":[]}

// 外部コマンドの呼び出しを辞める  [# Commit 45c868c](https://github.com/csnagu/private-isu-2025/commit/45c868ca5675e2332158446bec54803203b4c3cc)
{"pass":true,"score":10779,"success":10330,"fail":0,"messages":[]}

// MySQLの設定調整でINSERTを高速にする  [# Commit 6a29538](https://github.com/csnagu/private-isu-2025/commit/6a2953830ad02ed503b34ee8138e6a83c408038e)
{"pass":true,"score":11101,"success":10630,"fail":0,"messages":[]}

// memcachedへの問い合わせ回数を減らす  [# Commit e0bec0e](https://github.com/csnagu/private-isu-2025/commit/e0bec0eb9cb50761bf08031312ac772537bee288)
{"pass":true,"score":13561,"success":12983,"fail":0,"messages":[]}

// pprofを導入する  [# Commit cd10305](https://github.com/csnagu/private-isu-2025/commit/cd10305263781661c403697639bfc3c0bf73ffc8)
{"pass":true,"score":12296,"success":11786,"fail":0,"messages":[]}

// GoのTemplateを高速にする  [# Commit 4118f9a](https://github.com/csnagu/private-isu-2025/commit/4118f9adc150954d8be8b00287d0db4ba1dc5ba1)
{"pass":true,"score":16043,"success":15407,"fail":0,"messages":[]}

// json.Marshal(), json.Unmsharshal() を高速にする
{"pass":true,"score":16002,"success":15353,"fail":0,"messages":[]}

// account_nameにインデックスを貼る
{"pass":true,"score":16269,"success":15606,"fail":0,"messages":[]}

感想

ボトルネック箇所に順にアプローチしていくことでスコアを上げていくのが楽しい。 簡単にだがNginxやMySQLの設定を垣間見れた。パラメータが多すぎて本気のチューニングは大変そう。

Grafana Cloudでの可視化は無料枠でできて非常にお手軽だった。 主にPrometheusとGrafanaを使っていたが、他にもLokiやTempo, Pyroscopeも無料で使えるようで、どこかで使う機会があれば試してみたい。

pprofの可視化を途中から取り入れたが、もっと早くから取り入ればよかった程に役に立った。Goのアプリケーションでチューニングをするときにはぜひ使いたい。