Why P99 Latency is Lying to You (And What to Use Instead)

P99 is the standard. P99 is also misleading. Here's why histograms, P999, and error budgets give a much clearer picture of user experience.

1 апреля 2025 г.6 мин чтения
MetricsPercentilesSLOPrometheus

The P99 Problem

Your dashboard shows P99 latency = 250ms. Green. All good.

Meanwhile, 1 in 100 users is waiting 8 seconds for a response. At 1000 RPS, that's 10 users per second having a terrible experience. In a day: 864,000 bad experiences.

P99 told you everything was fine.

Why Percentiles Hide Problems

A percentile is a single number that summarizes a distribution. The problem: performance distributions are not smooth or symmetric.

Consider two services with identical P99 = 250ms:

Service A:

  • P50: 50ms, P90: 120ms, P99: 250ms, P99.9: 280ms
  • Fast service with a small tail
Service B:
  • P50: 50ms, P90: 120ms, P99: 250ms, P99.9: 12,000ms
  • Same P99, but 1 in 1000 requests takes 12 seconds
Same P99. Completely different user experience.

What to Use Instead

1. P99.9 and P99.99

One extra "9" reveals the outliers:

``yaml # Prometheus recording rules

  • record: api:request_duration_p999
expr: histogram_quantile(0.999, rate(http_request_duration_bucket[5m]))

  • record: api:request_duration_p9999
expr: histogram_quantile(0.9999, rate(http_request_duration_bucket[5m]))
`

If P99 = 250ms but P99.9 = 12s, you have a serious outlier problem.

2. Histograms, Not Summaries

Prometheus offers two metric types for latency: summaries and histograms.

Summary (avoid): ` http_request_duration_p99 0.250 `

  • Calculated client-side, cannot be aggregated across instances
  • P99 of (P99 of instance-1, P99 of instance-2) ≠ P99 of all requests
Histogram (use this): ` http_request_duration_bucket{le="0.1"} 8472 http_request_duration_bucket{le="0.25"} 9821 http_request_duration_bucket{le="1.0"} 9998 http_request_duration_bucket{le="+Inf"} 10000 `
  • Calculated server-side from bucket data
  • Fully aggregatable: histogram_quantile() works correctly across instances

3. Error Budgets (SLO-Based)

Instead of: "P99 < 250ms"

Use: "99.9% of requests complete in < 500ms, measured over 28 days"

The 0.1% budget = 40.3 minutes of allowed violations per 28 days.

`yaml # Alert when burn rate is too high

  • alert: ErrorBudgetBurnRate
expr: | ( rate(http_requests_total{status=~"5.."}[1h]) / rate(http_requests_total[1h]) ) > 0.001 # 0.1% error budget for: 5m `

The User Experience Test

When reviewing latency metrics, ask: "What does the worst 1 in 1000 users experience?"

If that number is acceptable, you're fine. If it's 10 seconds for a web page load, P99 being green means nothing.

Practical Dashboard Setup

` Grafana panels to add:

  • Latency heatmap (shows full distribution, not just percentiles)
  • P50 / P99 / P99.9 as separate lines
  • Error rate (5xx / total)
  • Apdex score (satisfied + tolerating / total)
  • ``

    The Apdex score is especially useful for stakeholder communication — it's a single 0-1 score that accounts for both fast and slow responses.

    Summary

    MetricUse CasePitfall
    |--------|----------|---------|
    P99General healthHides outliers
    P99.9SLO definitionNeeds high volume to be stable
    HistogramDeep analysisRequires bucket pre-configuration
    ApdexExecutive reportingNeeds threshold definition
    Error budgetSLO alertingRequires 28-day window for stability
    Хочешь попрактиковаться?
    Примени знания в интерактивном кейсе
    Смотреть кейсы →← Все статьи