達人が教えるWebパフォーマンスチューニング 〜ISUCONから学ぶ高速化の実践 読書メモ
https://scrapbox.io/files/674ced016bfcf030b5a608dc.png
1章
スループット
同時並行処理の性能
解像度の高さで見える論理的構造が全然違う
/emoji/tea.icon 高い方見ると確かにこんな感じで細分化できるな...と思った
キャパシティを調整するアプローチ
垂直or水平スケーリング
2章
外形監視と内部監視
top/free
cpuの使用率などを確認できる
3章
MySQLはスロークエリのログ記録設定ができる
Rows_sentとRows_examinedが実際に取得する数と、処理に必要な数
abコマンドの結果でリクエスト回数を見て、それに対して実行回数が多いクエリはN+1の可能性がある
バックエンドのアプリケーションの設定で、同時に処理できるリクエストを管理しているファイルを修正できる
4章
K6
jsに設定書ける
ChatGPTに出力してもらった
code:plain
running (0m30.0s), 0/10 VUs, 200 complete and 0 interrupted iterations
data_received................: 64 MB 557 kB/s
data_sent....................: 1.1 MB 9.5 kB/s
http_req_blocked.............: avg=4.57ms min=0s med=0s max=50ms p(90)=12ms p(95)=20ms
http_req_connecting..........: avg=3.33ms min=0s med=0s max=30ms p(90)=8ms p(95)=13ms
http_req_duration............: avg=50.98ms min=20ms med=30ms max=200ms p(90)=120ms p(95)=150ms
http_req_waiting.............: avg=48.91ms min=19ms med=28ms max=198ms p(90)=115ms p(95)=148ms
http_reqs....................: 200 6.666667/s
* data_received / data_sent: テスト中に受信・送信した合計データ量と、1秒あたりの平均転送量
- 64 MB 557 kB/s は合計64MB受信し、平均で557kB/sの速度でデータが流れていたことを示す
* http_req_blocked: リクエストが送信されるまでにブロックされていた時間
- avg=4.57ms は平均で4.57msリクエストが待たされていた
- min, med, max は最小値・中央値・最大値
- p(90), p(95) は90パーセンタイル・95パーセンタイル値で、90%/95%のリクエストがこの時間以下で完了したことを示す
* http_req_connecting: 接続確立までの時間
- サーバーへのTCP接続に要した時間の統計値
* http_req_duration: リクエスト応答全体の時間
- avg=50.98ms は平均で約51msでレスポンスが返ってきたことを示す
- p(90)=120ms は90%のリクエストが120ms以下で完了していることを示す
- 応答速度の概観をつかむための重要な指標
* http_req_waiting: サーバー処理に費やされた時間(サーバー側での待ち時間)
- リクエスト送信から最初のバイトが戻ってくるまでの期間
* http_reqs: 実行中に処理されたHTTPリクエスト数と1秒あたりの平均リクエスト数
* k6の実行結果は、リクエストがどれくらい高速・安定的に処理されたかを表すメトリクス
* avg、min、med、max、p(XX)といった統計値で性能特性を把握できる
* http_req_durationやhttp_req_waitingが短いほど応答が速く、http_reqsが多く処理できていればスループットが高い
評価
* http_req_duration(平均約51ms、最大200ms)
- 平均は50ms程度と比較的速そうに見えるが、最大値が200msと4倍も長く、遅い応答が時々発生していることがわかる
- p(90)=120ms, p(95)=150ms といった上位のパーセンタイル値が、平均よりかなり長い時間を示しているため、一部のリクエストで遅延が発生していることが疑われる
* http_req_blockedやhttp_req_connecting(平均数ms程度だが、maxやp(95)で2〜3倍に)
- 接続確立やリクエストブロックに多少ムラがある様子がうかがえる
- ただし数msレベルなので、致命的ではないかもしれない
* 一部リクエストが遅い:
- ネットワーク的な揺らぎ、またはサーバー側の処理で一部スローパス(遅いコードパス)が存在する可能性
* もし外部APIやDB接続があるなら、そのレスポンスのばらつきが影響しているかも
* 負荷(VUs=10, 30秒程度)に対してはそこまで多くないが、特定条件下で性能が劣化する可能性あり
* 平均値はそこそこだが、分布の上限値や上位パーセンタイル値が高めなので、不安定な部分がある
* 一部のリクエストで大きく遅れている原因を特定することが改善のポイント
5章:DBチューニング
Redis
OSSのNoSQL
/emoji/tea.iconという表現なんだ...
NewSQL
初めて聴いた
MySQLからNoSQLへ移行できない課題間から生まれたらしい
分析
topコマンドを実行したところ、MySQLのプロセスでCPUをかなり使用していることがわかったケース。
【重要:MySQLログイン後】
SHOW PROCCESSLIST;
SHOW FULL PROCCESSLIST;
MySQLを再起動せず、スロークエリログを可視化する方法
code:mysql
-- スロークエリログを有効にする
SET GLOBAL slow_query_log = 1;
-- ログファイルの場所を指定(必要に応じて変更)
SET GLOBAL slow_query_log_file = '/var/log/mysql/mysql-slow.log';
-- スロークエリの閾値を設定(例: 0秒)
SET GLOBAL long_query_time = 0;
永続化したい場合は、my.confに書く
pt-query-digest
brew install percona-toolkit
ChatGPTが作ったログサンプルに対して実行してみた
teeで書き出すのもありか
pt-query-digest /path/to/your/slow_query.log | tee "query_analysis_$(date +'%Y%m%d_%H%M%S').log"
code:plain
# 100ms user time, 10ms system time, 35.02M rss, 392.11G vsz
# Current date: Sat Dec 7 16:36:51 2024
# Hostname: horikoshiyuukinoMacBook-Air.local
# Files: /Users/yukihorikoshi/Downloads/sample_slow_query.log
# Overall: 1 total, 1 unique, 0 QPS, 0x concurrency ______________________
# Time range: all events occurred at 2024-12-07T12:00:00
# Attribute total min max avg 95% stddev median
# ============ ======= ======= ======= ======= ======= ======= =======
# Exec time 2s 2s 2s 2s 2s 0 2s
# Lock time 0 0 0 0 0 0 0
# Rows sent 10 10 10 10 10 0 10
# Rows examine 100 100 100 100 100 0 100
# Query size 834 834 834 834 834 0 834
# Profile
# Rank Query ID Response time Calls R/Call V/M
# ==== =================================== ============= ===== ====== ====
# 1 0x1EEA2E085D7671380535CE9D5736AF99 2.3450 100.0% 1 2.3450 0.00 SELECT users orders customers logs
# Query 1: 0 QPS, 0x concurrency, ID 0x1EEA2E085D7671380535CE9D5736AF99 at byte 0
# This item is included in the report because it matches --limit.
# Scores: V/M = 0.00
# Time range: all events occurred at 2024-12-07T12:00:00
# Attribute pct total min max avg 95% stddev median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count 100 1
# Exec time 100 2s 2s 2s 2s 2s 0 2s
# Lock time 0 0 0 0 0 0 0 0
# Rows sent 100 10 10 10 10 10 0 10
# Rows examine 100 100 100 100 100 100 0 100
# Query size 100 834 834 834 834 834 0 834
# String:
# Hosts localhost
# Users root
# Query_time distribution
# 1us
# 10us
# 100us
# 1ms
# 10ms
# 100ms
# 1s ################################################################
# 10s+
# Tables
# SHOW TABLE STATUS LIKE 'users'\G
# SHOW CREATE TABLE users\G
# SHOW TABLE STATUS LIKE 'orders'\G
# SHOW CREATE TABLE orders\G
# SHOW TABLE STATUS LIKE 'customers'\G
# SHOW CREATE TABLE customers\G
# SHOW TABLE STATUS LIKE 'logs'\G
# SHOW CREATE TABLE logs\G
# EXPLAIN /*!50100 PARTITIONS*/
SELECT * FROM users WHERE email LIKE '%@example.com%';
/icons/hr.icon
当日やれると良さそう
アクセスログのフォーマット
jsonにする
さらにそれをalpで見やすくする
alp
平均応答時間(AVG)が高いエンドポイントを特定。
最大応答時間(MAX)が異常に長い場合、パフォーマンスのボトルネックを疑う。
code:sh
# 1. NginxのJSONログファイルのパス
LOG_FILE="/var/log/nginx/access.json"
# 2. Nginxログが存在し、読み取り可能かチェック
echo "ログファイルが見つかりません: $LOG_FILE"
exit 1
fi
# 3. alpでログを解析するコマンド
# 必要に応じてエンドポイントをまとめる(例: /users/*)
alp --aggregates --sum -r -m "/users/*,/products/*" --file="$LOG_FILE"
# 終了メッセージ
echo "Nginxログ解析が完了しました。"
こういう感じ?
abコマンドの結果を見る
並列度とかを指定してURLにリクエストを投げられる
abとalpの結果を比較する
alpの実行結果のローテート
ローテートとは、ファイル名を実行ごとに変更してどの実行回のものか分かりやすくすること
code:sh
set -e
now=date +%Y%m%d-%H%M%S
mv /var/log/nginx/access.log /var/log/nginx/access.log.$now
systemctl reload nginx
こんな感じ
MySQLのスロークエリの表示
P76
負荷が上がるので、最後に解除するのを忘れず
バックエンドのアプリケーションの設定で、同時に処理できるリクエストを管理しているファイルを修正
code:ruby
# config/puma.rb
workers ENV.fetch("WEB_CONCURRENCY") { 2 } # プロセス数
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
threads threads_count, threads_count # スレッド数の最小・最大
preload_app!
puma
あとgoだとこんな感じ?
code:go
db.SetMaxOpenConns(10) // 最大接続数
db.SetMaxIdleConns(5) // アイドル状態で保持する接続数
k6を入れてシナリオでチェックできるようにする
テストデータをいろいろ用意すればできそうだが、結構大変
これは一回経験しないと難しそうかも
brew install percona-toolkit でpt-query-digestを入れて、DBのチューニング
pt-query-digest /path/to/your/slow_query.log | tee "query_analysis_$(date +'%Y%m%d_%H%M%S').log"
他の人のやっていること調査