← Workflows

阅读中文版本

API Latency SLO Tuning Workflow

Set percentile-based latency SLOs from traffic reality, decompose p99 into component spans, and tune the dominant path with measured verification.

advanced~40 minperformance-tuningsloapi

Objective

Bring API latency within its SLO by decomposing tail percentiles into their expensive components and fixing the dominant one with evidence.

Environment

  • Distributed tracing or per-stage timing on the API path
  • Historical latency distributions per endpoint
  • A stated SLO per endpoint class

Steps

1. Set SLOs from distribution reality

Define latency objectives per endpoint class from current distributions and user expectations, choosing p95 and p99 targets that are ambitious but not fictional.

Expected state: Each endpoint class has a percentile SLO with the distribution it came from.

2. Decompose the tail into spans

For endpoints violating their SLO at p99, break the request into spans (validation, downstream calls, serialization) and rank components by tail contribution.

Expected state: A span table shows which component owns the p99 blowout.

3. Tune the dominant component

Fix the highest tail contributor: parallelize independent calls, add caching where repeatable, cut payload size, or tighten a downstream timeout.

Expected state: The chosen fix targets the measured dominant span, not a guess.

4. Verify at the tail, not the mean

After deploying, compare p99 (and p99.9) for the endpoint against the SLO, watching for at least a full traffic cycle including peak.

Expected state: The tail percentile, not the average, is the verified metric.

5. Guard the gain with alerts and budgets

Wire SLO burn alerts on the tuned endpoints and record the achieved baseline for the next round.

Expected state: Regressions in the tuned tail page via burn-rate alerts.

Expected outcome

API endpoints within latency SLOs at the tail percentiles, with span-level evidence per fix, and burn alerts guarding the improvement.

Verification

Tuned endpoints hold p99 within SLO across a peak cycle; the tracing breakdown shows the dominant span's contribution collapsed.

The tail is a different request than the mean

Average latency describes the happy path; the SLO lives at p99, where different causes dominate: cache misses, slow shards, retry amplification. Decomposing the tail into spans finds which component owns the blowout, and tuning that one moves the SLO.

endpoint: GET /api/feed   SLO: p99 <= 500ms
field: p99 = 2.4s (violating), p50 = 90ms

span decomposition (p99 contributions):
  auth.token       8ms    (flat)
  feed.query       310ms  (cache-hit: 4ms)
                        cache-miss p99: 900ms <- DOMINANT
  media.presign    180ms  (per-item, N+1)
  serialize        22ms

fixes (ranked by tail contribution):
  1 feed.query: warm + stale-while-revalidate
    (miss rate 11% -> 0.4%)   p99 span: 900 -> 60ms
  2 media.presign: batch + 5min cache  180 -> 21ms

after (7d incl. peak): p99 = 380ms   SLO: MET

Cache misses live in the tail

A 95 percent cache hit rate sounds excellent and still owns the p99: the 5 percent of misses each pay the full origin cost. Tail decomposition separates the flat components from the bimodal ones (hit versus miss), and the bimodal spans are almost always where the SLO violation lives.

N-plus-one calls hide behind small numbers

A per-item call costing 180 milliseconds sounds tolerable until the fan-out multiplies it: twenty items, twenty calls, and the p99 inherits the worst of twenty draws. Batching or caching the shared prefix collapses the distribution, not just the mean.