Tech Learning Daily

2026-09-03 (Thu) — 第 49 号
AI が毎朝届ける、ソフトウェア技術の基礎解説
Perf 🌿 基礎 ⏱ 約 7 分

平均レイテンシが正常なのに、なぜ「たまに遅い」と言われるのか?

監視ダッシュボードのレイテンシ平均は今日も 100ms 前後で安定している。それなのに、ユーザーから「たまにアプリが固まる」という報告が絶えない。ダッシュボードは嘘をついていない。ただ、平均という数字が最初から見ないことにしている場所に、体感の遅さの正体が隠れているだけだ。

🎯 3 行まとめ
  • 平均レイテンシは分布の中心しか表さず、ごく一部の遅いリクエスト(テール)を覆い隠す。SRE の経験則では平均 100ms のサービスでも 1% が 5 秒かかることがある。
  • 1 つのユーザーリクエストが複数のバックエンドへ並列に問い合わせる「ファンアウト」構成では、各バックエンドの稀な遅延(p99)が、フロントエンドでは高頻度に観測されてしまう。
  • Little's Law(L = λW)は、待ち行列の長さが到着率と平均滞在時間の積で決まることを示し、使用率が上がるほど待ち時間が非線形に膨らむ理由を説明する。

平均という数字が隠しているもの

監視ダッシュボードに並ぶ「平均レイテンシ 98ms」という数字は、100 件のリクエストの応答時間を全部足して 100 で割っただけの値だ。この計算では、1 件だけ 5 秒かかったリクエストも、残り 99 件とまとめて均されてしまう。Google の SRE 本は「平均レイテンシ 100ms のサービスでも、1% のリクエストは 5 秒かかっていることがある」と述べている。平均だけを見ている限り、この 1% の存在に気づく方法はない。

そこで使われるのがパーセンタイルという指標だ。100 件のリクエストの応答時間を速い順に並べたとき、下から 50 番目の値が p50(中央値)、下から 95 番目が p95、下から 99 番目が p99 になる。p99 が 1,200ms だとすれば「100 件に 1 件は 1,200ms 以上かかっている」という意味であり、これは平均値には一切含まれていない情報だ。

100件を速い順に並べたときの応答時間
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
50件目     95件目        99件目
 p50        p95           p99
100ms      280ms        1200ms

ファンアウトが「稀な遅延」を「よくある遅延」に変える

1 台のサーバーの p99 が悪くても、それを呼び出すのが 1 回だけなら、ユーザーの体感としては「100 回に 1 回」の確率でしか遭遇しない。一見、無視できる誤差に思える。しかし実際の Web サービスでは、1 件のユーザーリクエストが裏側で複数のマイクロサービスやシャードへ並列に問い合わせるファンアウトという構成を取ることが多く、話はそう単純では済まない。

Google の "The Tail at Scale" 論文は、各サーバーが 1% の確率で遅延するとして、それを 100 台に並列でファンアウトした場合、「少なくとも 1 台が遅延する」確率が 1 − 0.99100 ≈ 63% に達すると示している。ユーザーのリクエストは並列に呼んだ全員の応答を待つ必要があるため、この 63% がそのままユーザー体感のレイテンシに跳ね返る。同論文はこの現象を「バックエンドの p99 が、フロントエンドの中央値になる」と表現している。稀なはずのテールが、ファンアウトを経ることで大多数の体験になってしまうということだ。

Little's Law — 待ち行列はなぜ急に膨らむのか

テールレイテンシが生まれる典型的な原因の 1 つが、サーバー内部の待ち行列だ。Little's Law は待ち行列理論の基本法則で、系の中にいる処理待ち・処理中の件数の平均 L が、到着率 λ(1 秒あたり何件到着するか)と系内での平均滞在時間 W の積で決まる(L = λW)ことを示す。

式自体は単純だが、実務上大事なのは W が λ に対して線形には増えないという点だ。スレッドプールやコネクションプールの使用率(どれだけ埋まっているか)が 50% 程度のうちは、多少リクエストが増えても滞在時間はほとんど伸びない。しかし使用率が 80%、90% と上がるにつれて、ちょっとした到着の偏りが処理待ちの列を急激に伸ばし、W が跳ね上がる。平常時は問題なく動いていたサービスが、負荷が少し増えただけで急に p99 だけ悪化するのは、この非線形性のせいだ。

🍱 たとえるなら

スーパーのレジを思い浮かべてほしい。客が来る頻度が上がるほど、あるいはレジ 1 件あたりの会計時間が延びるほど、レジ前に並ぶ人数は増える。稼働率が 50% 程度のレジなら、多少混んでもすぐ行列は解消する。しかし稼働率が 80% を超えたあたりから、ほんの一人の会計トラブルが行列全体を雪だるま式に伸ばし始める。サーバーのリクエストキューも、これと同じ力学で動いている。

p99 を設計の判断材料にする

テールレイテンシの存在を知ったところで、それをどう使うかが実務では問われる。1 つの使い方がタイムアウト値の決定だ。AWS の Builders' Library は、下流サービスへのタイムアウトを「許容できる誤検知率(例えば 0.1%)」を決めたうえで、それに対応するパーセンタイルのレイテンシを閾値にすることを勧めている。平均値や勘でタイムアウトを決めると、正常なリクエストまで誤ってタイムアウトさせてリトライの嵐を招いたり、逆に緩すぎてクライアントを長時間待たせたりする。

もう 1 つの使い方が SLO(Service Level Objective)の設定だ。「平均レイテンシ 200ms 以下」ではなく「p99 レイテンシ 500ms 以下を 99.9% の時間で満たす」のようにパーセンタイルを目標値に据えることで、平均の裏に埋もれていた劣化を検知できるようになる。

💼 実務でどう出会うか

Datadog や New Relic、CloudWatch のような APM ツールは、レイテンシのダッシュボードに p50 / p95 / p99 を並べて表示する機能を標準で持っている。オンコール対応で「p99 が SLO を超えました」という形でアラートが飛んでくるのは、まさにこの記事で説明した理由による。マイクロサービス構成で分散トレーシングを導入すると、どのサービスの遅延がファンアウトの起点になっているかを 1 リクエスト単位で追えるようになる。

⌨️ 手を動かす(5 分)

手元の環境から適当な HTTPS サイトへ 50 回リクエストを送り、応答時間を集めて自分で p50 / p95 / p99 を計算してみる。

for i in $(seq 1 50); do
  curl -s -o /dev/null -w "%{time_total}\n" https://www.google.com
done | sort -n | awk '
  { a[NR] = $1 }
  END {
    print "p50:", a[int(NR*0.50)]
    print "p95:", a[int(NR*0.95)]
    print "p99:", a[int(NR*0.99)]
  }'

p50 と p95 はそれほど差が無くても、p99 だけ極端に飛び抜けた値になることがある。これは自宅や社内ネットワークのわずかな輻輳・DNS キャッシュミス・TLS 再ハンドシェイクなど、めったに起きない要因がその 1 回にだけ重なった結果で、平均を見ているだけでは絶対に気づけない挙動だ。

🙅 よくある誤解
  • 平均レイテンシが速ければユーザーの体感も速い — ユーザー体験は分布のテールに引っ張られやすく、特にファンアウト構成では稀な遅延が高頻度の体験に変わる。
  • p99 は外れ値のノイズだから無視してよい — 1 台あたりでは稀な遅延でも、100 台にファンアウトすればリクエストの大半がどこかの p99 に当たる。
  • Little's Law は理論上の話で実務には関係ない — スレッドプールやコネクションプールの適正サイズを決めるといった、並列数の設計判断に直結する。
📖 用語ミニ辞典
パーセンタイル
値を昇順に並べたとき、指定した割合の位置に来る値。p99 なら下から 99% の位置。
テイルレイテンシ
分布の裾(テール)にあたる、ごく一部の遅いリクエストの応答時間。
ファンアウト
1 件のリクエストが複数のサーバー・サービスへ並列に問い合わせを展開する構成。
Little's Law
待ち行列理論の基本法則。系内の平均件数 L は到着率 λ と平均滞在時間 W の積(L = λW)で決まる。
使用率
スレッドプールやコネクションプールなど、処理資源がどれだけ埋まっているかを表す割合。
🔗 もっと深く