遅いという言葉を数字に変える
一言でいうと
「遅いです」では、何も直せません。何が・いつ・どのくらい・何パーセント遅いのかに言い換えた瞬間、調査範囲が10分の1になります。
なぜ必要なのか
報告は、次のような形で届きます。「最近、システムが遅いです」。
ここですぐにサーバーに入ってCPUを見るのが、最もよくあるミスです。CPUが30%なら「正常なんですが」で終わり、90%なら「CPUのせいです」と誤って終わります。どちらも答えではありません。
まず尋ねるべきことは、4つです。
| 質問 | 理由 | 答えが絞り込むもの |
|---|---|---|
| どの画面・機能か | 全部が遅いことはまれです | コードパス |
| いつからか | デプロイや設定変更と突き合わせます | 原因の時点 |
| どのくらいか(以前は2秒、今は20秒) | 10倍なのか10%なのかを見ます | 問題の性質 |
| 常にか、たまにか | 平均の問題なのか、テールの問題なのかを見ます | 調査方法 |
特に4つ目です。常に遅いのと、たまに遅いのとでは、原因が違います。常に遅いなら構造(クエリ、N+1、同期呼び出し)の問題で、たまに遅いなら競合(ロック、コネクションプール、GC、ディスク)の問題です。この2つを混ぜて見ると、平均に埋もれて何も見えません。
区間に分ける
リクエスト1つが通る道を分けて、各区間の時間を測ります。
클라이언트 → [네트워크] → 웹서버 → [앱] → [DB] → 앱 → 웹서버 → 클라이언트
測定ツールがなくても、curl1つで区間を分けられます。
curl -o /dev/null -s -w \
'dns=%{time_namelookup} conn=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' \
https://example.com/slow-page
dnsが大きい → 名前解決です。リゾルバーの設定、キャッシュミスを疑います。connが大きい → TCP接続です。経路、ファイアウォール、SYN待ちを疑います。tlsが大きい → ハンドシェイクです。証明書チェーン、OCSPの照会を疑います。ttfbが大きく、total - ttfbは小さい → サーバーが考えている時間です。アプリまたはDBです。total - ttfbが大きい → 本文の転送です。レスポンスが大きいか、帯域幅の問題です。
この5つの数字だけで、「ネットワークかサーバーか」が分かれます。ほとんどの議論は、ここで終わります。
平均を信じない
平均レスポンスが200msのサービスで、ユーザーが「遅い」と言うのはよくあることです。1000件のうち950件が50msで、50件が3秒なら、平均は197msです。平均は正常なのに、20人に1人は3秒待たされています。
そこで、パーセンタイルを見ます。
| 指標 | 意味 | 用途 |
|---|---|---|
| p50 (中央値) | 半分がこれより速い | 典型的な体験 |
| p95 | 20人に1人が経験する値 | 体感的な不満の始まり |
| p99 | 100人に1人 | テール。競合・GC・リトライ |
| max | 最悪の1件 | 外れ値の追跡 |
p50は変わらないのにp99だけが上がったなら、容量ではなく競合です。p50から一緒に上がったなら、構造か容量です。
負荷とレイテンシは別の軸
CPU使用率60%は、「余裕が40%」という意味ではありません。待ち行列理論では、利用率が上がると待ち時間は線形ではなく急激に増えます。70%を超えると、少し集中しただけでレイテンシが数倍になります。そのため、「まだCPUに余裕がありますよ」は、レイテンシの問題への反論になりません。
現場での姿
- 「ネットワークが遅いようです」という声は、
ttfbを測ってみると、ほとんどがサーバーの処理時間です。 - 午前9時だけ遅い場合は、出勤時間帯の同時接続と、キャッシュが空になっている時刻が重なっています。
- 特定の顧客だけ遅い場合は、その顧客のデータ量が違い、クエリがフルスキャンになっています。
絞り込んだあとに何を測るのか
区間を分けてサーバー側だと分かったら、次はその中で何が待っているのかを見ます。ここでよくあるミスは、CPU使用率1つで判断することですが、前のコースで見たとおり、サーバーが待つ時間の大半は、CPUの順番待ちではありません。
次の4つを、順番に除外していきます。
1つ目は実行待ちです。実行できる状態のものがコア数より多いかを見ます。ロードアベレージが高いのにCPU使用率が低いなら、こちらではありません。コンテナなら、前に見たスロットリングも一緒に見る必要があります。平均使用率は低いのにテールレイテンシだけが跳ねる、典型的な原因です。
2つ目は入出力待ちです。ディスクやネットワークを待つ時間です。データベースが遅いのも、たいていここで、そのとき直すのはアプリケーションではなくクエリやインデックスです。
3つ目はロックと待ち行列です。コネクションプール、スレッドプール、アプリケーションのロック、データベースの行ロックです。同時リクエストが増えたときだけ遅くなるなら、ほぼ必ずここであり、1人でテストしても再現しないのが特徴です。
4つ目は外部呼び出しです。こちらが呼んでいる別のサービスが遅いのです。このとき、こちら側で直せるのはタイムアウトとリトライとフォールバックだけなので、原因と対応が分かれます。
この4つを切り分ける最も安い方法は、負荷を変えてみることです。リクエストを1つだけ送っても遅いなら構造の問題で(1つ目か2つ目)、同時に複数を送ったときだけ遅くなるなら競合です(3つ目)。この1回の比較で、調査範囲が半分になります。
そして、直したあとに同じ方法でもう一度測ります。直したと信じることと、数字が変わったことは別で、最初に測った値がなければ、良くなったと言える根拠がありません。
続くラボですること
340件の実際のリクエストログを受け取り、4つの質問に自分で答えます。答えはすべてデータに入っているので、順番に切り分けていくだけで、「遅いです」の1行が、1つのバージョンを指すレポートになります。