Percentile Metrics: Đo lường Latency Đúng Cách

Hướng dẫn sử dụng percentile (P50, P95, P99) thay vì average để đo lường latency, phân biệt vai trò P95 vs P99, và chiến lược alerting theo percentile.

percentilep99p95monitoringslolatency
Cập nhật: 23 tháng 10, 2026

Tại sao Average là chỉ số sai lầm

Hãy xem xét hai service cùng xử lý HTTP request:

MetricService A (stable)Service B (long-tail)
Average51ms50ms
P9554ms390ms ⚠️
P9958ms920ms 🔥

Average của hai service gần như giống hệt nhau (~50ms), nhưng P95 của Service B gấp 7 lần Service A! Điều này nghĩa là cứ 20 request thì có 1 request của Service B mất gần 400ms — một trải nghiệm tệ mà average hoàn toàn giấu đi.

Tương tự như đo nhiệt độ cơ thể: bạn nói "nhiệt độ trung bình 37°C — bình thường" trong khi tay 35°C và trán 40°C. Average che giấu hình dạng phân phối (shape of distribution). Trong monitoring, điều bạn thực sự cần biết là "bao nhiêu phần trăm user đang chịu latency tệ?"

Để hiểu thêm mối quan hệ giữa latency và throughput, xem Latency & Throughput Overview.

Percentile là gì

Percentile (phân vị) được tính bằng cách sắp xếp tất cả giá trị theo thứ tự tăng dần. P(N) là giá trị mà tại đó N% request có latency nhanh hơn hoặc bằng giá trị đó.

text
100 requests, sorted ascending:
Request #50  → 12ms    ← P50 (median)
Request #95  → 85ms    ← P95 (5% slower than this)
Request #99  → 340ms   ← P99 (1% slower than this)
Request #100 → 920ms   ← Max

Ý nghĩa thực tế: nếu P99 = 340ms, nghĩa là 99% request hoàn thành trong 340ms hoặc nhanh hơn, chỉ có 1% chậm hơn con số này.

Tác động thực tế với 1 triệu request/ngày

Mỗi percentile violation ảnh hưởng đến một số lượng user khác nhau. Dù tỷ lệ nhỏ, với traffic lớn thì con số tuyệt đối rất đáng kể:

Với 1 triệu request/ngày: 50,000 request vượt P95 threshold, 10,000 request vượt P99, và 1,000 request vượt P99.9. Đó là 50K user bị ảnh hưởng mỗi ngày — không hề ít.

P95 vs P99: Vai trò khác nhau

P95 và P99 không chỉ khác nhau về con số — chúng có vai trò hoàn toàn khác nhau trong hệ thống monitoring:

Đang tải sơ đồ...
Vai tròP99P95
Ẩn dụ🔍 Smoke Detector🚨 Fire Alarm
Phát hiệnSớm, nhạyMuộn hơn, chắc chắn
Hành độngĐiều tra, theo dõiHành động ngay
Noise levelCao hơn (false positive)Thấp (real problem)

Kịch bản thực tế: Memory Leak

Khi ứng dụng bị memory leak, GC (Garbage Collection) phải chạy nhiều hơn và mỗi lần chạy lâu hơn. Ban đầu chỉ 1% request bị ảnh hưởng bởi GC pause — P99 tăng trước. Sau 1-2 giờ, khi memory áp lực nhiều hơn, GC chạy liên tục hơn — lúc này P95 mới bắt đầu tăng.

P99 phát hiện memory leak sớm hơn P95 từ 1-2 giờ. Đó là khoảng thời gian quý giá để điều tra và fix trước khi vấn đề lan rộng thành incident.

Chiến lược Alerting theo Percentile

Một hệ thống alerting hiệu quả cần nhiều tầng, mỗi tầng percentile có threshold riêng. Dưới đây là reference thresholds cho Web API thông thường:

PercentileSLO Target⚠️ Warning🚨 Critical
P50 (median)< 50ms> 80ms> 150ms
P95 (fire alarm)< 200ms> 350ms> 500ms
P99 (smoke detector)< 500ms> 800ms> 1500ms

Lưu ý: Các threshold trên là giá trị tham khảo. Mỗi service cần calibrate dựa trên baseline thực tế và yêu cầu business. Một batch processing API có thể chấp nhận P99 = 5s, trong khi payment API cần P99 < 300ms.

yaml
# Prometheus alerting rules - Web API
groups:
  - name: latency_slo
    rules:
      # P50 — User experience baseline
      - alert: LatencyP50Warning
        expr: histogram_quantile(0.50, rate(http_duration_seconds_bucket[5m])) > 0.080
        for: 5m
        labels: { severity: warning }

      - alert: LatencyP50Critical
        expr: histogram_quantile(0.50, rate(http_duration_seconds_bucket[5m])) > 0.150
        for: 3m
        labels: { severity: critical }

      # P95 — Fire Alarm: vấn đề đã lan rộng
      - alert: LatencyP95Warning
        expr: histogram_quantile(0.95, rate(http_duration_seconds_bucket[5m])) > 0.350
        for: 5m
        labels: { severity: warning }

      - alert: LatencyP95Critical
        expr: histogram_quantile(0.95, rate(http_duration_seconds_bucket[5m])) > 0.500
        for: 3m
        labels: { severity: critical }

      # P99 — Smoke Detector: phát hiện sớm
      - alert: LatencyP99Warning
        expr: histogram_quantile(0.99, rate(http_duration_seconds_bucket[5m])) > 0.800
        for: 10m
        labels: { severity: warning }

      - alert: LatencyP99Critical
        expr: histogram_quantile(0.99, rate(http_duration_seconds_bucket[5m])) > 1.500
        for: 5m
        labels: { severity: critical }

Chú ý rằng P99 alert có for duration dài hơn (10m thay vì 3-5m) vì P99 nhạy hơn và dễ bị noise. P95 critical chỉ cần 3m vì khi P95 tăng, vấn đề đã rõ ràng.

Khi thiết lập benchmark, hãy cẩn thận với hiện tượng Coordinated Omission — lỗi đo lường phổ biến khiến percentile bị đánh giá thấp hơn thực tế.

Phân phối xác suất của Latency

Một sai lầm phổ biến là giả định latency tuân theo Normal Distribution (phân phối chuẩn). Thực tế, latency trong production hầu như luôn theo Log-normal hoặc Multi-modal Distribution.

Đang tải sơ đồ...

Nguyên nhân tạo Long Tail

  • GC pauses: JVM full GC có thể gây stop-the-world hàng trăm ms đến vài giây
  • Cache misses: Request đầu tiên hoặc sau khi cache expire phải query database
  • TCP retransmissions: Packet loss buộc TCP phải retransmit, thêm RTT delay
  • Context switching: OS scheduler chuyển thread, gây thêm latency không dự đoán được
  • Disk I/O: Page fault hoặc swap memory gây latency spike đột ngột

Coefficient of Variation (C = σ/μ)

Coefficient of Variation (C) đo mức độ biến thiên tương đối của latency: C = σ / μ (độ lệch chuẩn chia trung bình).

  • C < 0.5: Phân phối tương đối ổn định, P95 và P99 không quá xa P50
  • C ≈ 1: Bắt đầu xuất hiện hiệu ứng hockey-stick — latency tăng vọt ở percentile cao
  • C > 1: Long tail rất rõ rệt, P99 có thể gấp 10-100 lần P50

Khi C > 1, hiệu ứng hockey-stick xuất hiện sớm hơn nhiều so với kỳ vọng. Đây là dấu hiệu hệ thống cần được tối ưu tail latency — xem thêm Tail Latency at Scale và mô hình toán học tại Little's Law & Queueing Theory.

Công cụ và cấu trúc dữ liệu cho Percentile

Tính percentile chính xác yêu cầu giữ toàn bộ data trong memory — không khả thi với hàng triệu request. Các cấu trúc dữ liệu sau cho phép tính approximate percentile trong streaming với bộ nhớ cố định:

Đang tải sơ đồ...
Cấu trúc dữ liệuĐặc điểmSử dụng bởi
t-digestChính xác cao ở extreme percentile (P99, P99.9). Mergeable.Elasticsearch, Datadog
HDR HistogramFixed memory, configurable precision. Logarithmic bucketing.JVM tools, wrk2
DDSketchRelative error guarantee. Mergeable. Mới nhất.Datadog (native)

Quy tắc vàng: Không bao giờ Average Percentile

Đây là lỗi nghiêm trọng và phổ biến nhất khi làm việc với percentile: bạn không thể lấy trung bình P99 của nhiều instance rồi gọi đó là P99 tổng. Phải merge raw histogram data trước, rồi mới tính percentile trên tập hợp đó.

python
# ❌ SAI: Average percentile across instances
p99_avg = (instance1_p99 + instance2_p99 + instance3_p99) / 3

# ✅ ĐÚNG: Merge raw histogram buckets, rồi tính percentile
# Prometheus tự động làm điều này khi dùng histogram_quantile()
# với label aggregation:
histogram_quantile(0.99,
  sum(rate(http_duration_seconds_bucket[5m])) by (le)
)

Lý do: percentile không có tính chất additivity. Trung bình của các percentile có thể cho ra giá trị thấp hơn hoặc cao hơn so với percentile thực. Prometheus giải quyết vấn đề này bằng cách lưu histogram bucket và tính quantile tại query time.

Khi benchmark các công cụ trên, cần lưu ý hiện tượng Coordinated Omission — nhiều benchmark tool mắc lỗi này khiến kết quả percentile bị sai lệch nghiêm trọng.