private-isu作業集
mysqlのスロークエリログ設定
/etc/mysql/mysql.conf.d/mysql.cnf
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 0
systemctl restart mysql
インデックス作成
alter table comments add index post_id_idx(post_id, created_at desc);
アプリケーションの並列数を変更
webapp/ruby/unicorn_config.rb
を編集
worker_processes 4
goではたぶんこれ
func main() {
// CPUコア数を取得
numCPU := runtime.NumCPU()
// 使用するCPUコア数を設定
runtime.GOMAXPROCS(numCPU)
// または、固定のコア数を指定したい場合
// runtime.GOMAXPROCS(4) // 例えば、4コアを使用する場合
// 以下、アプリケーションの処理
}
alpでアクセスログ出力
alp json \
--sort sum -r \
-m "/posts/[0-9]+,/@\w+,/image/\d+" \
-o count,method,uri,min,avg,max,sum \
< /var/log/nginx/access.log
- スコアが上昇して、ベンチマーカーが大量のリクエストを送るようになると開けるファイルディスクリプタの上限数を超えてしまうことがある。
too many open files
が発生したときはベンチマーカーのインスタンスで/etc/security/limits.conf
に以下を追記し、ログインしなおして設定を反映。ulimit -n
コマンドで設定確認
isucon hard nofile 10000
isucon soft nofile 10000
ISUCON本でのprivate-isuの大まかな流れ
- まずは初期状態でベンチマーカーを実行し、
top
コマンドでCPU利用率を調査 - mysqlのCPU消費率が約100%でボトルネック
- DB操作に問題がありそうなので、スロークエリログを解析
pt-query-digest
,mysqldumpslow
- クエリの問題箇所に応じてインデックス貼ったりする
- 再度
top
コマンドで確認 - CPU利用率がコアごとに偏っていて並列で使えていない
- アプリケーションのworkerプロセスを4にして改善されていることを確認
- alpでアクセス集計ログを確認
- ボトルネックが大きい箇所から対応 (静的ファイル配信など)
- ボトルネック解消→
alp
でアクセスログ確認したりpt-query-digest
でスロークエリを確認したりexplain
で実行計画調べて対処、の繰り返し - アプリケーションコードやsqlのチューニング以外にも、DBの最大接続数やプリペアドステートメントの改善(
interpolateParams=true
)、dbコミット時のログ書かないようしたり、ネットワークのコネクションのオーバーヘッドやカーネルやハードウェアの設定変更したりするのも忘れずに
以下自分で試しにやってみる
チートシート作ってみた
- nginxをjsonでログ出力し、alpでnginxへのアクセスログを見やすくした
- mysqldを設定してスロークエリログを出すようにした
- スロークエリログをpt-query-digestで見れるようにした
- 実装をgoに変更した
1回目のベンチマーカー実行
スコア
{"pass":true,"score":2200,"success":2153,"fail":2,"messages":["リクエストがタイムアウトしました (GET /)","リクエストがタイムアウトしました (POST /login)"]}
top 見るとmysqlが〜200%ボトルネック
mysqlがボトルネックになる原因
- クエリが非効率(インデックス貼っていないなど)
スロークエリをpt-query-digestで調査
# Profile
# Rank Query ID Response time Calls R/Call V/M Ite
# ==== ============================= ============== ===== ====== ===== ===
# 1 0x624863D30DAC59FA16849282... 478.9479 68.8% 1845 0.2596 0.02 SELECT comments
# 2 0x422390B42D4DD86C7539A5F4... 158.7321 22.8% 1978 0.0802 0.01 SELECT comments
# 3 0x100EC8B5C400F34381F9D7F7... 38.2365 5.5% 133 0.2875 0.01 SELECT comments
# MISC 0xMISC 20.6842 3.0% 37694 0.0005 0.0 <23 ITEMS>
上から順に解決して、ベンチマーカーを回してみることにする
1つ目のボトルネックの解消:インデックス作成
ボトルネックとなるクエリ
SELECT * FROM `comments` WHERE `post_id` = 9998 ORDER BY `created_at` DESC LIMIT 3\G
post_idで選択した後、created_atでソートしているので、(post_id, created_at desc)で複合インデックスを貼って試してみる
alter table comments add index post_id_idx (post_id, created_at desc);
CPU利用率めっちゃ下がって効いてそう。アプリケーションにもCPUが割かれている
スコアも〜20000点
{"pass":true,"score":19365,"success":18248,"fail":0,"messages":[]}
ボトルネック解消後のpt-query-digestの結果を見てみる
# Profile
# Rank Query ID Response time Calls R/Call V/M Ite
# ==== ============================ ============== ====== ====== ===== ===
# 1 0x624863D30DAC59FA1684928... 493.0000 48.4% 54101 0.0091 0.25 SELECT comments
# 2 0x422390B42D4DD86C7539A5F... 171.5244 16.8% 56848 0.0030 0.07 SELECT comments
# 3 0x4858CF4D8CAA743E839C127... 123.6508 12.1% 2009 0.0615 0.01 SELECT posts
# 4 0x19759A5557089FD5B718D44... 42.4770 4.2% 36129 0.0012 0.02 SELECT posts
# 5 0x100EC8B5C400F34381F9D7F... 39.3179 3.9% 2747 0.0143 0.26 SELECT comments
# 6 0xDA556F9115773A1A99AA016... 35.0950 3.4% 305842 0.0001 0.00 ADMIN PREPARE
# 7 0x396201721CD58410E070DA9... 30.0628 3.0% 151084 0.0002 0.00 SELECT users
# 8 0x7A12D0C8F433684C3027353... 29.8516 2.9% 426 0.0701 0.01 SELECT posts
# 9 0xCDEB1AFF2AE2BE51B2ED5CF... 25.2236 2.5% 443 0.0569 0.01 SELECT comments
# MISC 0xMISC 28.2069 2.8% 339371 0.0001 0.0 <34 ITEMS>
一番上のクエリに関して、R/Callが0.26→0.09になっており効率化ができている。
select comments
の呼び出し件数がめっちゃ多くなっている。N+1問題が発生している可能性あり。
その解消に行く前に定番のADMIN PREPAREを解消する
おまけにpost_idだけにインデックス貼った時のスコアがこちら
{"pass":true,"score":18973,"success":17894,"fail":0,"messages":[]}
複合インデックスじゃなくてもよさそう?
2つ目のボトルネック解消:ADMIN PREPAREの解消
これは定番のやつ。
アプリケーションのDB接続箇所にinterpolateParams=true
を追記する。
dsn := fmt.Sprintf(
- "%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=true&loc=Local",
+ "%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=true&loc=Local&interpolateParams=true",
スコア下がった
3つ目:N+1の解消
select comment
がめっちゃ実行されているので、N+1が発生している可能性。
アプリケーションのコードを見に行く。
remote ssh接続後
sudo chmod -R 777 ./
pprotein導入手順
基本はこれ:
importに
"github.com/kaz/pprotein/integration/standalone"
// 省略
func main() {
go func() {
standalone.Integrate(":19000")
}()
// 省略
}
起動したらインスタンスではなくローカルのPCからこれ
ssh -i "private-isu.pem" ubuntu@{ipアドレス} -fNL 9000:localhost:9000
pproteinのsettingのhttpのところ
[
{
"Type": "pprof",
"Label": "localhost",
"URL": "http://localhost:19000/debug/pprof/profile",
"Duration": 60
},
{
"Type": "httplog",
"Label": "localhost",
"URL": "http://localhost:19000/debug/log/httplog",
"Duration": 60
},
{
"Type": "slowlog",
"Label": "localhost",
"URL": "http://localhost:19000/debug/log/slowlog",
"Duration": 60
}
]