データ・分析

アクセスログを pandas で分析する — 遅いページと離脱を数字で見つける

「サイトが重い気がする」「どのページで離脱しているのか分からない」。
解析ツールを導入する前に、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 の集計から、リンク切れと攻撃試行の両方が読める
  • ここまで全部、追加のツールを入れずにできる

高機能な解析ツールを入れる前に、この集計を一度回すだけで
直すべき場所の優先順位がはっきりします。