What p99 means
p99 is the 99th percentile of a latency distribution. It means: 99% of requests complete faster than this value, and 1% take longer. It is sometimes written as the "99th percentile latency" or "99p".
At 100 requests per second, p99 latency affects roughly 1 request per second. At 1,000 req/s, 10 requests per second hit or exceed the p99 threshold. These are not rare events — they're a continuous stream.
| percentile | meaning | at 1,000 req/s |
|---|---|---|
| p50 | Median — half of requests are slower, half faster | 500 req/s exceed this |
| p95 | 95th percentile — 5% of requests are slower | 50 req/s exceed this |
| p99 | 99th percentile — 1% of requests are slower | 10 req/s exceed this |
| p999 | 99.9th percentile — 0.1% of requests are slower | 1 req/s exceeds this |
Where to look in your APM tools
Most observability tools expose percentile latency. Here's where to find it in the common ones:
# Prometheus / Grafana — histogram_quantile histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) # Datadog — p99 latency metric avg:trace.express.request.duration.by.resource_name{*}.as_rate().rollup(p99, 60) # CloudWatch — ExtendedStatistics { "ExtendedStatistics": ["p99"], "MetricName": "Duration" } # New Relic NRQL SELECT percentile(duration, 99) FROM Transaction WHERE appName = 'myapp'
If you don't have real latency data yet, slowdep's built-in presets are calibrated from production observations for common services like 'postgres', 'redis', 'openai', and 'stripe'. Start there and refine when you have data.
Configuring a custom profile from your data
Once you have your p50 and p99 from APM, plug them directly into slowdep's custom profile:
import { withLatency } from 'slowdep'; // Your APM shows: p50 = 12ms, p99 = 340ms, error rate ~0.3% const slowQuery = withLatency(db.query, { p50: 12, p99: 340, errorRate: 0.003, }); // Now your tests reflect actual production behaviour const result = await slowQuery('SELECT ...');
Why p99 determines your safe timeout value
Your request timeout must be larger than your p99, or you'll start rejecting legitimate requests. The usual rule of thumb is to set your timeout at roughly p999 — covering 99.9% of real responses while cutting off true hangs.
// Service: p50=5ms, p99=200ms, p999=2000ms // Timeout too tight — rejects 1% of real requests const TIMEOUT = 150; // below p99, will fire on valid responses // Timeout correct — only catches true hangs const TIMEOUT = 2000; // at p999, covers 99.9% of legitimate responses // Test the timeout fires when expected: // mock Math.random to force a high-percentile sample jest.spyOn(Math, 'random').mockReturnValue(0.999); // forces near-p99 sample