達人が教える web パフォーマンスチューニング ISUCON から学ぶ高速化の実践
第3章 基礎的な負荷試験
private-isu の起動
テスト用の web サービス private-isu をチューニングしてみよう。 AWS で動かしても良いが docker で動かしてみる。 まずはアプリケーションサーバーを起動する。
gh repo clone catatsuy/private-isu
cd private-isu
# テーブル初期データセット
cd webapp/sql
curl -L -O github.com/catatsuy/private-isu/releases/download/img/dump.sql.bz2
bunzip2 dump.sql.bz2
# サービス起動
ca webapp
docker compose up
# 動作確認
open localhost
ここで初期データセット忘れてた場合は後から入れないといけない。 パスワードは docker-compose に環境変数として宣言されてるのでそれを使う。
docker exec -it webapp-mysql-1 bash
mysql -p < docker-entrypoint-initdb.d/dump.sql
負荷試験の準備
nginx の設定がデフォルトのままなので、まずはアクセスログの設定をしてみる。 nginx.conf を眺めてみる。
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
情報は最低限で combine 形式のログになっている。実際のログは下のような感じ。
webapp-nginx-1 | 172.18.0.1 - - [10/Jul/2022:00:50:22 +0000] "GET /image/9980.jpg HTTP/1.1" 200 95919 "localhost" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36" "-"
今回は読みやすさ重視でログフォーマットを json にしてみる。
nginx.conf は docker の都合上書き換えずに /etc/nginx/conf.d/default.conf
を編集してみる。
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",'
'"referrer": "$http_referer",'
'"ua": "$http_user_agent",'
'"request_time": "$request_time",' # ユーザのリクエストからレスポンス返すまでの時間
'"response_time": "$upstream_response_time"}'; # nginx のリクエストからアプリケーションサーバーがレスポンス返すまでの時間
server {
listen 80;
client_max_body_size 10m;
root /public/;
location / {
proxy_set_header Host $host;
proxy_pass app:8080;
}
access_log /var/log/nginx/access.log json;
}
nginx を再起動する。
docker exec -it webapp-nginx-1 nginx -s reload
下のような感じで json フォーマットに切り替わった。
webapp-nginx-1 | {"time": "2022-07-10T01:19:29+00:00","host": "172.18.0.1","port": "64810","method": "GET","uri": "/favicon.ico","status": "304","body_bytes": "0","referrer": "localhost/@rosa","ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36","request_time": "0.000","response_time": "0.001"}
ログを加工したりなんだりする上でログが nginx コンテナの中で閉じていると面倒。 標準出力ではなくて docker のボリュームに書き出すようにしたい。 docker-compose.yml を下のような感じで書き換える。
volumes:
- ./etc/nginx/conf.d:/etc/nginx/conf.d
- ./public:/public
- ./logs/nginx:/var/log/nginx
docker compose up を停止してやりなおすとボリュームが割り当てられる。 これでホストの logs/nginx/access.log 宛にログが書き出されるようになる。 次はログの解析のためのツール alp を入れてみる。
brew install alp
alp json --file logs/nginx/access.log
負荷試験の実行
apache benchmark => ab コマンドを試していたがちょっと面倒くさそうなのでやらないことにした。
パフォーマンスチューニング
性能指標には単位時間に処理できるリクエスト数(スループット)を使うことが多い。 ベンチマーク実行中に top コマンドで CPU の利用状態とかをみるといい。 一番CPUを消費しているプロセスがボトルネックになっていることが多い。
mysql の設定を変えて、クエリを出してみよう。 etc/my.cnf を編集して下の行を追加。
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 0
longquerytime が 0 なのはとりあえず全部のクエリを観察してみるという意図。 N+1 クエリとかは見つけやすくなると思う。あとで調整する。mysql を再起動してみよう。 でもスロークエリは出ない。ちゃっかりエラーが出ていた。
webapp-mysql-1 | 2022-07-10T02:03:07.368768Z 0 [ERROR] [MY-011263] [Server] Could not use /var/log/mysql/mysql-slow.log for logging (error 2 - No such file or directory). Turning logging off for the server process. To turn it on again: fix the cause, then either restart the query logging by using "SET GLOBAL SLOW_QUERY_LOG=ON" or restart the MySQL server.
どうやらログファイルがなくてエラーになっているようだ。 docker で mysql を動かしていると mysql のプロセスを再起動するのが無理。 仕方がないのでこのログファイルも host のボリュームをマウントして事前に作っておくことにする。
mkdir -p logs/mysql
touch logs/mysql/mysql-slow.log
出てるスロークエリの例は下のような感じ。
SELECT * FROM `posts` WHERE `id` = 9979;
# Time: 2022-07-10T02:31:22.342442Z
# User@Host: healthchecker[healthchecker] @ localhost [] Id: 10
# Query_time: 0.000156 Lock_time: 0.000000 Rows_sent: 0 Rows_examined: 0