At a glance
Query Latency p99 (ms) is the statement execution time at the 99th percentile: 99% of queries in the window finished faster than this number, and the slowest 1% finished slower. It is the tail-latency card, and the tail is where users feel pain. A healthy p50 with an ugly p99 means most requests are fine but a meaningful slice of sessions stall, which is exactly the experience that drives cart abandonment and timeout retries. p99 is the number an SRE watches when “the database feels slow” but the averages look clean.
Calculation
Conceptually, p99 is the value at the 99th position when every statement’s execution time in the window is sorted ascending. In practice the engine does not sort millions of rows; it derives the percentile from the Performance Schema digest distribution.TRUNCATE or server restart, so the engine takes deltas between samples to keep the p99 anchored to the trailing 5-minute window rather than to all-time history.
Worked example
A platform team runs a MySQL 8.0 primary behind a product-catalogue and checkout API. Snapshot taken on 14 Apr 26 at 13:05 BST during a flash-sale ramp.
The headline shows p99 = 610ms with a red border, and a Nerve Centre alert fires. The team reads the spread:
- p50 is 4ms but p99 is 610ms, a 150x gap. This is a classic tail problem, not a broad regression. If the whole instance were overloaded, p50 would have moved too. Because it has not, the cause is concentrated in a handful of statements.
- They open Top 10 Slowest Queries. The top digest is an unindexed
WHERE status = ? AND created_at > ?filter doing a full scan on theorderstable, which only hurts under flash-sale write volume because the buffer pool keeps evicting the hot pages. - They check InnoDB Buffer Pool Hit Rate, which has slipped from 99.6% to 97.1%. The cold reads from the full scan are evicting hot pages, so even well-indexed queries occasionally pay a disk read, which fattens the tail further.
orders (status, created_at) plus a temporary bump to innodb_buffer_pool_size. After the index ships, p99 settles back to 70ms and the card clears.
Three takeaways:
- Watch the gap, not just the number. A p99 of 610ms with a p50 of 4ms is a different problem from a p99 of 610ms with a p50 of 300ms. The first is a few bad queries; the second is a saturated instance.
- p99 leads errors. Tail latency climbing toward your application timeout is the early warning before 504s appear in the app logs. Acting on a red p99 is cheaper than acting on a customer-facing outage.
- Tail problems are usually fixable without scaling. An index, a query rewrite, or a buffer-pool nudge resolves most p99 spikes. Reach for a bigger instance only after Top 10 Slowest Queries shows the tail is spread across many digests, not concentrated in one.
Sibling cards
Reconciling against the source
Where to look in MySQL’s own tooling:Performance Schema digest table for the per-statement distribution this card is built from:Why our number may legitimately differ from a manual query:Thesysschema for a friendlier rollup:SELECT * FROM sys.statements_with_runtimes_in_95th_percentile;The slow query log (withlong_query_timeset low) for the raw statements that make up the tail. Managed-service consoles: Amazon RDS / Aurora Performance Insights exposes a database-load-by-wait breakdown and per-statement latency that should track this card.
Cross-connector reconciliation: pair with your APM connector (for example Datadog p99 Latency). The APM number measures end-to-end request latency including network and application time, so it will be higher than the database-only p99 here. A divergence where the database p99 is flat but the APM p99 spikes points to an application or network problem, not the database.
Known limitations / FAQs
My average query time looks fine but p99 is red. Which do I trust? Trust p99. Averages hide the tail by design: one million 2ms queries plus a thousand 5-second queries still averages out to a few milliseconds. Your users do not experience the average; the unlucky 1% experience the tail, and during a sale that 1% can be hundreds of abandoned checkouts. The average is the number that makes a slow database look healthy. Why is the window 5 minutes and not real-time per query? A pure real-time p99 would swing wildly on a single slow statement. The 5-minute trailing window smooths transient spikes while still reacting fast enough to catch a real regression within one refresh. It is the standard tail-latency window for OLTP alerting. The card reads zero or “no data”. Why? Almost always because Performance Schema is disabled or the statement instruments are off. CheckSELECT @@performance_schema; (must be 1) and confirm events_statements_summary_by_digest is being collected. On some managed tiers Performance Schema is off by default and must be enabled in the parameter group, after which it needs a restart.
Does this include queries on read replicas?
The card measures the node the connector is pointed at. If you connect to the source, you see source-side latency; if you point at a replica, you see replica latency. Replicas often have a different p99 because their workload is read-heavy and they replay the binlog in the background. Connect a separate Vortex IQ source per node if you want both.
Can I change the 500ms alert threshold?
Yes. The threshold is configurable per profile in the Alert Rules tab. A reporting database can tolerate a higher p99; a real-time checkout database may want it tighter, for example 250ms. Set it to a value just above your normal busy-hour p99 so the card only fires on genuine regressions.
p99 spiked but Top 10 Slowest Queries looks normal. What happened?
Two common causes. First, a lock wait or a deadlock: the query itself is cheap but it blocked behind a lock, so the latency is real but the digest looks innocent. Check InnoDB Deadlocks. Second, a buffer-pool eviction storm: a normally cached query paid a disk read. Check InnoDB Buffer Pool Hit Rate.
Should I alert on p95, p99, or both?
Both, at different thresholds. p95 (200ms here) catches a slowdown spreading into a meaningful slice of traffic; p99 (500ms) catches the tail before it turns into timeouts. p95 red on its own is a “look soon” signal; p99 red is an “act now” signal because it sits closest to your application timeout.