← Recipes

阅读中文版本

Analyze Tail Latency Outliers Beyond the Averages

Compute P50, P99, and P99.9 from raw latency samples, group outliers by instance and endpoint, and find the one slow replica dragging every hundredth request.

~30 minapiperformanceobservability

Task

Build a latency histogram from raw samples, isolate P99+ outliers by dimension, and identify the systemic cause behind them.

When to use this

The average response time is 80 ms and looks fine, yet users complain about slowness. The pain lives in the tail that the average statistic never shows.

Prerequisites

  • Raw per-request latency samples with dimensions such as instance and endpoint
  • A spreadsheet, Python, or jq for percentile math
  • Enough samples for P99.9 to be meaningful, at least tens of thousands

Steps

1. Extract raw latencies with a grouping dimension.

jq -r '[.latency_ms, .instance, .endpoint] | @tsv' metrics.jsonl | sort -n > samples.tsv

Expected output: A sorted TSV of latency, instance, and endpoint ready for percentile cuts.

2. Compute percentiles across the whole service.

awk -F'\t' '{a[NR]=$1} END {print "p50", a[int(NR*0.5)], "p99", a[int(NR*0.99)], "p999", a[int(NR*0.999)]}' samples.tsv

Expected output: p50 82, p99 1900, p999 4200: the median is healthy while the tail is twenty times worse.

3. Slice the top one percent by instance.

awk -F'\t' 'NR > total*0.99 {print $2}' total=$(wc -l < samples.tsv) samples.tsv 2>/dev/null | sort | uniq -c | sort -rn

Expected output: If one instance owns most of the top bucket, a slow replica rather than the code path explains the tail.

4. Slice the same bucket by endpoint.

head -1 samples.tsv >/dev/null; tail -n +$(( $(wc -l < samples.tsv) * 99 / 100 )) samples.tsv | cut -f3 | sort | uniq -c | sort -rn | head

Expected output: One endpoint dominating the tail points to a specific slow query or lock contention instead of infrastructure.

5. Correlate outlier timestamps with background events.

tail -n +$(( $(wc -l < samples.tsv) * 99 / 100 )) samples.tsv | cut -f1 | sort -n | awk 'NR>1 {print $1-prev} {prev=$1}' | sort | uniq -c | sort -rn | head -3

Expected output: Outliers arriving at regular spacing, such as every 60 seconds, implicate cron jobs or cache expirations rather than random noise.

Troubleshooting

Problem

Not enough samples for P99.9

Solution

Lower the ambition to P99 until volume supports it, or widen the window; a P99.9 from a few hundred samples is fiction.

Problem

Tail is flat across all dimensions

Solution

The cause is shared and external, such as garbage-collection pauses or disk contention; check host-level metrics at outlier timestamps next.

Problem

Outliers cluster at whole seconds

Solution

Look for fixed timeouts upstream; a one-second pile usually means something timed out and something else retried it.

Limitations

  • Percentiles describe the distribution, not any individual request; tracing is still needed to explain a specific outlier.
  • Aggregating pre-computed averages destroys tails forever; only raw samples support this analysis.

The average is a mask, the tail is the truth

Averages hide exactly the requests users remember: the one checkout that took four seconds among thousands of eighty-millisecond page views. Sorting raw samples and cutting percentiles reveals the shape of the pain, then grouping the worst bucket by instance, endpoint, and timing separates a slow replica from a slow query from a cron collision. Every one of those has a different owner and a different fix.

# Percentiles from raw samples
jq -r '[.latency_ms, .instance, .endpoint] | @tsv' metrics.jsonl | sort -n > samples.tsv
awk -F'\t' '{a[NR]=$1} END {print "p50", a[int(NR*0.5)], \
  "p99", a[int(NR*0.99)], "p999", a[int(NR*0.999)]}' samples.tsv

Group the tail, do not just measure it

Knowing P99 is 1.9 seconds changes nothing. Knowing the top bucket belongs to one instance retires the machine; knowing it belongs to one endpoint fixes a query; knowing it repeats every sixty seconds reschedules a job. Always cut the tail by a dimension before forming a hypothesis, or you will optimize the median while the tail stays untouched.

Refuse pre-aggregated data

Percentiles of percentiles, and averages of averages, are mathematically meaningless and systematically optimistic. Insist on raw per-request samples with dimensions attached. The pipeline cost of shipping them is the cost of ever knowing why your slowest users are slow.