「サイトが重い気がする」「どのページで離脱しているのか分からない」。
解析ツールを導入する前に、Nginx のアクセスログだけでかなりのことが分かります。
ログに応答時間を出す
デフォルトのログには応答時間が入っていません。まずフォーマットを変えます。
log_format timed '$remote_addr - [$time_local] "$request" '
'$status $body_bytes_sent $request_time $upstream_response_time';
access_log /var/log/nginx/access.log timed;
$request_time… リクエスト受信から応答完了まで(回線の遅さを含む)$upstream_response_time… アプリの処理時間だけ
この2つを分けて記録するのが肝心です。
前者だけ遅い場合は回線やファイルサイズ、後者が遅い場合はアプリの問題と切り分けられます。
pandas に読み込む
import pandas as pd
df = pd.read_csv(
"access.log", sep=" ", header=None, engine="python",
names=["ip", "_", "time", "req", "status", "bytes", "req_time", "up_time"],
na_values="-",
)
df["path"] = df["req"].str.split(" ").str[1]
df["up_time"] = pd.to_numeric(df["up_time"], errors="coerce")
ログ形式は環境で違うので、names は自分のフォーマットに合わせます。
平均ではなく分位点を見る
平均応答時間はほぼ役に立ちません。 大半が速いと、遅い一部が埋もれます。
slow = (
df.groupby("path")["up_time"]
.agg(n="count", p50="median", p95=lambda s: s.quantile(0.95))
.query("n >= 50")
.sort_values("p95", ascending=False)
.head(20)
)
見るのは p95(遅いほうから5%)です。
「20回に1回、3秒待たされる」ページはユーザーの体感を確実に損ないます。
n >= 50 の条件も重要で、これが無いと1回しか呼ばれていないページが上位を占めます。
エラーの発生箇所を出す
errs = df[df["status"] >= 500]
print(errs.groupby("path").size().sort_values(ascending=False).head(10))
500 系が特定のパスに偏っていれば、そこにバグがあります。
404 の集計も有用で、外部からのリンク切れや
存在しないファイルを狙う攻撃が見えます。
df[df["status"] == 404]["path"].value_counts().head(20)
# → /.env, /wp-login.php などが並ぶ = 攻撃の試行
時間帯の傾向
df["hour"] = pd.to_datetime(df["time"], format="[%d/%b/%Y:%H:%M:%S", errors="coerce").dt.hour
df.groupby("hour").size().plot(kind="bar")
ピーク時間が分かると、バッチ処理をいつ流すべきかが決まります。
まとめ
- ログに
$request_timeと$upstream_response_timeを両方出す - 平均ではなく p95 を見る。件数の下限も必ず設ける
- 404 の集計から、リンク切れと攻撃試行の両方が読める
- ここまで全部、追加のツールを入れずにできる
高機能な解析ツールを入れる前に、この集計を一度回すだけで
直すべき場所の優先順位がはっきりします。