API Latency Percentiles (p50 to p99.9) Markdown Table Template
A percentile table describes the shape of a latency distribution per endpoint, which an average cannot. The median says what a typical request experiences; p95 and p99 say what the unlucky ones experience, and in a busy service “unlucky” means thousands of people an hour. These tables appear in load-test reports, release regression checks, quarterly performance reviews and capacity plans, read by service owners, SREs and the engineers deciding where optimisation time goes.
Edit This Template
Edit cells, add rows, or sort — the Markdown updates livePaste cells from Excel or Google Sheets into the first header to replace the table · Tab / Enter / arrows move · Shift+F10 opens row & column actions
When to Use This Table
Tail latency matters more than its small share suggests because requests fan out. If a page calls ten backends in parallel and each independently has a 1% chance of exceeding its p99, the chance that at least one is slow is 1 − 0.99¹⁰, roughly 9.6%. Your users see the slowest dependency, so a backend’s p99 quickly becomes a frontend’s p90. That is why p99 gets its own highlighted column.
A table worth trusting states its measurement context: the time window, where latency was measured (inside the server, at the load balancer, or in the client), how many requests each row covers, and what load the service was under. Numbers from different windows or different measurement points cannot share a column. Client-side timings include network and TLS time that server-side ones never see.
Formatting This Table in Markdown
A api latency percentiles (p50 to p99.9) table puts a few things in cells that Markdown treats as syntax. These are the ones to watch so it renders correctly on GitHub, GitLab, Obsidian and docs sites.
Wrap endpoint paths, especially templated ones
Keep method and path in one code span, as in
GET /users/<id>. Bare,<id>is read as an HTML tag and disappears, and the two asterisks in/api/*/orders/*italicize the text between them.Flag SLO breaches with text or symbols, not colour
GitHub strips
styleattributes and<font>, so a red p99 cell is not possible. Bold the breaching value, add ⚠️, and state the target in the header (for examplep99 (SLO 300 ms)) so the flag explains itself.Right-align error rates and fix the decimals
Centered numbers line up only when every value has the same width. Right-align Error Rate like the latency columns, give it a fixed number of decimals (
0.00%,0.15%), and use thousands separators (1,850) wherever a value passes a thousand.Eight columns is wide: split throughput and error rate
Column width cannot be set in GFM, and every header adds to the minimum width. Keep the endpoint and the percentiles in the main table, put throughput and error rate in a second one, or shorten headers (
p99.9,RPS).
What Goes in Each Column
| Column | Alignment | What to put here |
|---|---|---|
| Microservice Endpoint | left :--- | HTTP method plus the route template in backticks, such as GET /api/v1/tables/{id}, never a concrete URL containing IDs, which would split one endpoint across many rows. Left-aligned. Group or sort rows by service so owners can find their own. |
| Throughput (RPS) | right ---: | Mean requests per second over the stated window, right-aligned with thousands separators. Say whether it is mean or peak. This column also tells readers how much evidence sits behind each percentile: 120 RPS over an hour is 432,000 requests, while 2 RPS over ten minutes is only 1,200. |
| p50 (Median) | right ---: | Half of requests completed at or below this time. Right-aligned, and the same unit in every latency column; putting “ms” in the header and leaving bare numbers in cells keeps them sortable. A rising median means the whole endpoint got slower, not just its outliers. |
| p90 | right ---: | Nine in ten requests finish within this time. Comparing it with p50 shows how wide the main body of the distribution is: a p90 several times the median usually points to cache misses, cold paths or uneven payload sizes rather than rare stalls. |
| p95 | right ---: | A common SLO threshold and the percentile many dashboards display by default. Right-aligned, same precision as its neighbours. When p95 drifts but p50 does not, look at contention: connection pools, lock waits, or a downstream dependency that is occasionally slow. |
| p99 (Tail) | right ---: | One request in a hundred is slower than this. Highlight values that breach the SLO in bold. A p99 must come from the merged distribution for the endpoint; averaging per-instance p99 values produces a number that corresponds to no real percentile. |
| p99.9 | right ---: | One request in a thousand is slower than this. Show it only when the row has enough samples in the window — tens of thousands at least — and write a dash otherwise, because a handful of requests would decide it. Bold SLO breaches as for p99. |
| Error Rate | center :---: | Failed requests divided by all requests in the same window, as a percentage with two decimals. Define “failed” under the table — 5xx only, or timeouts and client-visible 4xx too — and say whether failed requests are included in the latency columns. |
How to Fill It In
- 1Fix the window and the measurement point first, then pull every row from that same window. For Prometheus histograms the usual query is
histogram_quantile(0.99, sum by (le, route) (rate(http_request_duration_seconds_bucket[5m]))), which sums bucket counts across instances before estimating the quantile. - 2If the numbers come from a load test, use an open-model generator that keeps a constant arrival rate (wrk2, or k6’s
constant-arrival-rateexecutor). Closed-loop tools wait for each response before sending the next request, so a stall delays the requests that would have measured it — the coordinated-omission problem — and the tail looks better than it is. - 3Record the request count for each row and check it supports the highest percentile shown. Below a few thousand samples p99 rests on a handful of requests; drop or footnote the column rather than publish a precise-looking guess.
- 4Pull the error rate with the same filters and window, and note whether timed-out requests were recorded at the timeout value or excluded.
- 5Compare each row with its SLO or the previous release, bold the breaches, and add a delta column if the table supports a go/no-go decision. The Markdown table sorter will order rows by p99 once cells hold bare numbers.
Ready-to-Paste Variations
Release regression check (before vs after)
For a canary or load-test comparison between two builds under identical traffic. Units in the header keep cells numeric and sortable.
| Endpoint | p50 before (ms) | p50 after (ms) | p99 before (ms) | p99 after (ms) | Δ p99 |
| :--- | ---: | ---: | ---: | ---: | ---: |
| `POST /api/v1/tables/parse` | 18 | 19 | 78 | 81 | +4% |
| `POST /api/v1/tables/latex` | 24 | 23 | 112 | **168** | **+50%** |
| `GET /api/v1/analytics/query` | 85 | 84 | 640 | 615 | −4% |SLO compliance and error budget
Expresses each objective as a share of requests under a threshold, which is how latency SLOs are usually written, and shows how much of the error budget the window consumed.
| Endpoint | Objective (28 days) | Requests | Within threshold | Budget used | Status |
| :--- | :--- | ---: | ---: | ---: | :---: |
| `POST /api/v1/tables/parse` | 99% under 300 ms | 2,056,320,000 | 99.40% | 60% | OK |
| `POST /api/v1/auth/session` | 99.9% under 100 ms | 2,661,120,000 | 99.85% | **150%** | **Breach** |Deep tail with sample counts
Adds the slowest observed request and the sample count that justifies showing p99.9 and max. Low-traffic rows show a dash instead of an unsupported estimate.
| Endpoint | Samples (1 h) | p50 (ms) | p99 (ms) | p99.9 (ms) | Max (ms) |
| :--- | ---: | ---: | ---: | ---: | ---: |
| `GET /api/v1/health` | 15,120,000 | 2 | 8 | 19 | 240 |
| `POST /api/v1/tables/parse` | 3,060,000 | 18 | 78 | 205 | 1,930 |
| `GET /api/v1/admin/export` | 6,000 | 410 | 2,350 | — | 3,100 |Common Mistakes
Averaging p99 values across pods, regions or one-minute intervals to get a “fleet p99”.
Percentiles do not compose. Merge the underlying histograms (sum Prometheus bucket counts, or merge HdrHistogram or t-digest sketches) and compute the quantile once from the combined data.
Publishing load-test tails from a closed-loop generator that pauses while the server is stalled.
Rerun with a constant arrival rate, or correct for coordinated omission, and state the target request rate in the report.
Showing p99.9 for an endpoint that served a few hundred requests in the window.
Add a sample-count column and replace unsupported percentiles with a dash. Lengthen the window for low-traffic endpoints instead.
Excluding timed-out requests from latency, so the tail improves exactly when the service degrades.
Record timeouts at the timeout duration or report them in the error column with a footnote, and keep the rule identical across releases.
Related Markdown Table Templates
Browse all 50 templatesCI/CD Pipeline Stages & Automation Gates
Document multi-stage CI/CD workflows across linting, unit testing, security scanning, container builds, and production deployments.
Cron Job Schedule Syntax Reference
Cheat sheet for 5-field crontab scheduling expressions, field ranges, special wildcard characters, and human frequency interpretations.
Company & Team Quarterly OKRs Matrix
Objectives & Key Results governance table tracking strategic focus, baseline metrics, quarterly goals, confidence ratings, and leads.
API Latency Percentiles (p50 to p99.9): Frequently Asked Questions
What does p99 latency actually mean?
It is the value below which 99% of requests in the window completed. It is not the average of the slowest 1%, and it says nothing about how bad the remaining 1% are; that is what p99.9 and max are for.
Why report percentiles instead of the average response time?
Latency distributions are skewed, with a long right tail. A few very slow requests drag the mean upward while most users see something faster, and a growing tail can hide inside a stable mean. Percentiles describe the typical case and the tail separately.
Can I average percentiles from several servers?
No. The average of per-host p99 values is not the p99 of the combined traffic and can be badly wrong when hosts carry different loads. Collect histograms or mergeable sketches, add them together, and compute the percentile from the merged result.
How many requests do I need before p99.9 means anything?
Only one request in a thousand lies above p99.9, so 10,000 samples leave about ten requests defining it. Aim for tens of thousands per row, and with fewer, report p99 and the maximum instead.
How accurate are percentiles computed from histogram buckets?
Prometheus interpolates within the bucket containing the quantile, so the answer can only be as precise as the bucket boundaries. Place boundaries near your SLO threshold, or use a native or high-dynamic-range histogram when you need finer resolution in the tail.