延遲百分位數 (P50/P95/P99) 與吞吐量取捨
TL;DR。P50、P95、P99 是「延遲的百分位數 (percentile)」,用來描述一群請求的延遲分布, 而不是只看平均值。吞吐量 (throughput) 與延遲 (latency) 之間確實常有取捨,但不是單純的反比關係, 下面會說明這點。
什麼是百分位數延遲
假設你收集了一段時間內所有請求的延遲,把它們由小到大排序,那麼:
- P50 (中位數):有 50% 的請求比這個值快。代表「典型」的使用者體驗。
- P95:有 95% 的請求比這個值快,只有最慢的 5% 比它慢。
- P99:有 99% 的請求比這個值快,只有最慢的 1% 比它慢。
舉個具體例子,假設你有 100 個請求,把延遲排序後:
- 第 50 個請求的延遲 = P50
- 第 95 個請求的延遲 = P95
- 第 99 個請求的延遲 = P99
P99 = 200ms 的意思是:「99% 的請求都在 200ms 內完成,只有最慢的 1% 超過 200ms。」
為什麼不直接看平均值 (average)?
平均值會被分布的形狀「騙」。延遲分布通常是右偏 (long tail,長尾) 的, 少數非常慢的請求 (例如 GC 暫停、cache miss、鎖競爭、重試) 會把平均值拉高或藏起來。
舉例來說,下面兩組系統的平均值可能一樣,但使用者體驗天差地遠:
| 系統 | 平均 | P50 | P99 |
|---|---|---|---|
| A | 50ms | 48ms | 60ms |
| B | 50ms | 20ms | 900ms |
系統 B 雖然平均也是 50ms,但有 1% 的使用者要等將近 1 秒,這在實務上常常是無法接受的。 百分位數能讓你看到「最糟的那批使用者」的體驗,這也是平均值看不到的。
P99 為什麼重要 — 尾延遲 (tail latency)
在大型系統中,一個使用者的操作往往需要呼叫很多個後端服務。 如果一個頁面要 fan-out 呼叫 100 個服務,而每個服務的 P99 是 1%, 那麼這個頁面至少碰到一次「慢請求」的機率會高達約 63% (1 - 0.99^100)。
換句話說,後端的 P99 可能就是前端的 P50。這就是為什麼大型系統會特別在意尾延遲 (tail latency)。
小提醒:百分位數不能直接相加或平均。多台機器各自的 P99 取平均,並不等於整體的 P99。 要算整體百分位數,必須把原始數據 (或 histogram) 合併後再計算。
延遲與吞吐量的取捨
在某些情況下「吞吐量越高,延遲越高,反之亦然」。
它不是一條普遍成立的反比定律,而是在特定情況下才會出現的現象。下面拆解三個重點。
重點一:在低負載時,兩者並不衝突
當系統還很閒 (低 utilization) 時,你可以同時擁有高吞吐量與低延遲。 增加吞吐量 (每秒處理更多請求) 並不會讓延遲變差,因為資源還夠用、沒有人需要排隊。
所以「吞吐量高 → 延遲一定高」並不是任何時候都成立。它真正會成立,是在系統接近滿載的時候。
重點二:接近滿載時,延遲會「爆炸」(排隊理論)
真正的取捨來自排隊 (queueing)。當請求到達的速度逼近系統的最大處理能力 (capacity) 時,
請求開始排隊,等待時間急遽上升。根據排隊理論,延遲大致與 1 / (1 - utilization) 成正比:
- utilization = 50% → 等待時間係數約 2x
- utilization = 90% → 約 10x
- utilization = 99% → 約 100x
也就是說,當你把吞吐量推到接近系統極限 (utilization → 100%),延遲會趨近無限大。 這就是為什麼實務上不會把系統跑在 100% 使用率,通常會保留 headroom (餘裕)。
關聯的概念是 Little's Law:L = λ × W,其中 L 是系統內平均的請求數、
λ 是到達率 (吞吐量)、W 是平均停留時間 (延遲)。在系統內請求數固定的情況下,吞吐量與延遲互相牽制。
重點三:刻意用延遲換吞吐量 — 批次處理 (batching)
很多系統會主動犧牲延遲來換吞吐量,最典型的就是批次處理 (batching):
- 攢一批請求一起處理 (例如資料庫的 group commit、Kafka 的 batch、GPU 的 batch inference), 可以攤平固定成本、提升整體吞吐量。
- 但每個請求都得「等湊滿一批」,所以單一請求的延遲變高。
其他類似的取捨:
- Nagle's Algorithm (TCP):把小封包攢起來一起送,減少封包數 (提升效率),但增加延遲。
- 更大的 buffer / queue:能吸收突發流量、提升吞吐量,但讓排隊延遲變長 (參考 bufferbloat 問題)。
總結吞吐量與延遲的關係
延遲與吞吐量不是單純的反比關係。
- 在低負載時,可以同時提升兩者,並不衝突。
- 取捨真正浮現在接近滿載時:把吞吐量推向系統極限,延遲會急遽 (非線性地) 惡化。
- 也可以刻意用延遲換吞吐量 (如 batching),反之亦然 (減少 batch 來壓低延遲,但犧牲吞吐量)。
- 兩者也可以同時被改善:升級硬體、優化演算法、增加平行度,能同時提高吞吐量並降低延遲。 它們是兩個獨立的指標,只是在資源受限時會互相拉扯。
實務上怎麼用
- 訂 SLO/SLA 時,用百分位數而非平均值,例如「P99 延遲 < 200ms」。
- 監控時同時看 P50 (典型體驗) 與 P99 (最差體驗),兩者差距大代表有長尾問題。
- 做容量規劃 (capacity planning) 時,保留 headroom,不要把吞吐量推到接近 100% 使用率。
- 想壓低尾延遲常見手法:限制 fan-out、加 timeout + 重試 (hedged requests)、隔離慢路徑、避免 head-of-line blocking。
參考資料
- The Tail at Scale (Dean & Barroso, ACM)
- Latency vs. throughput
- 《Designing Data-Intensive Applications》Chapter 1 — Percentiles