Skip to content
Data & AI Template8 columns · 5 sample rows· Updated

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 live
Row
1
GET /api/v1/health
4,200
2 ms
4 ms
5 ms
8 ms
19 ms
2
POST /api/v1/tables/parse
850
18 ms
32 ms
45 ms
78 ms
205 ms
3
POST /api/v1/tables/latex
420
24 ms
41 ms
58 ms
112 ms
290 ms
4
GET /api/v1/analytics/query
120
85 ms
190 ms
310 ms
640 ms
1,850 ms
5
POST /api/v1/auth/session
1,100
12 ms
21 ms
29 ms
46 ms
120 ms
Output
| Microservice Endpoint         | Throughput (RPS) | p50 (Median) |      p90 |      p95 | p99 (Tail) |      p99.9 | Error Rate |
| :---------------------------- | ---------------: | -----------: | -------: | -------: | ---------: | ---------: | :--------: |
| `GET /api/v1/health`          |          `4,200` |       `2 ms` |   `4 ms` |   `5 ms` |     `8 ms` |    `19 ms` |   0.00%    |
| `POST /api/v1/tables/parse`   |            `850` |      `18 ms` |  `32 ms` |  `45 ms` |    `78 ms` |   `205 ms` |   0.02%    |
| `POST /api/v1/tables/latex`   |            `420` |      `24 ms` |  `41 ms` |  `58 ms` |   `112 ms` |   `290 ms` |   0.01%    |
| `GET /api/v1/analytics/query` |            `120` |      `85 ms` | `190 ms` | `310 ms` |   `640 ms` | `1,850 ms` |   0.15%    |
| `POST /api/v1/auth/session`   |          `1,100` |      `12 ms` |  `21 ms` |  `29 ms` |    `46 ms` |   `120 ms` |   0.00%    |
5 rows × 8 columns
Rendered Preview
Microservice EndpointThroughput (RPS)p50 (Median)p90p95p99 (Tail)p99.9Error Rate
GET /api/v1/health4,2002 ms4 ms5 ms8 ms19 ms0.00%
POST /api/v1/tables/parse85018 ms32 ms45 ms78 ms205 ms0.02%
POST /api/v1/tables/latex42024 ms41 ms58 ms112 ms290 ms0.01%
GET /api/v1/analytics/query12085 ms190 ms310 ms640 ms1,850 ms0.15%
POST /api/v1/auth/session1,10012 ms21 ms29 ms46 ms120 ms0.00%

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 style attributes and <font>, so a red p99 cell is not possible. Bold the breaching value, add ⚠️, and state the target in the header (for example p99 (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

ColumnAlignmentWhat to put here
Microservice Endpointleft :---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.
p90right ---: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.
p95right ---: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.9right ---: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 Ratecenter :---: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

  1. 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.
  2. 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-rate executor). 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.
  3. 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.
  4. 4Pull the error rate with the same filters and window, and note whether timed-out requests were recorded at the timeout value or excluded.
  5. 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.

Browse all 50 templates
FAQ

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.