Skip to main content
Metrics type: Key MetricsCategory: Cross-Platform: Revenue at Risk

At a glance

Slow Queries During Checkout Window (5m) lines up two signals that usually live in different teams’ dashboards: the count of slow PostgreSQL statements firing right now, and whether the storefront’s checkout completion rate has dipped at the same moment. Each row is one slow statement (query text, mean execution time, call count, calling application) captured inside the trailing 5-minute window. The card exists to answer one question a DBA and an ecommerce lead keep arguing about: “is the database the reason orders just stopped completing?” When more than 5 slow queries co-occur with a measurable checkout drop, the card fires and tells you the slowdown is not academic, it is costing orders this minute.

Calculation

The card is computed in two passes and then joined on the clock. Pass one, the database side. Vortex IQ reads pg_stat_statements and isolates statements where mean_exec_time > slow_threshold_ms within the trailing 5-minute window. Because pg_stat_statements accumulates totals since the last pg_stat_statements_reset(), the engine works from deltas: it snapshots calls, total_exec_time, and mean_exec_time per queryid at the start and end of the window and reports only the statements that were actually slow during those 5 minutes, not statements that were slow last week and have since been fixed. Each surviving statement becomes one row: normalised query text, mean execution time in milliseconds, call count in the window, and the calling application_name from pg_stat_activity where it can be matched. Pass two, the storefront side. In parallel, the attached commerce connector reports checkout completion for the same 5-minute clock: the ratio of orders created to checkouts initiated, or the raw order rate, depending on what the platform exposes. Vortex IQ compares that ratio against the trailing baseline for that time of day and day of week. The join. The two passes are aligned on the same UTC 5-minute bucket. The alert (> 5 slow queries co-occur with a checkout drop) fires only when both conditions hold in the same bucket: the slow-statement count exceeds 5 and checkout completion has fallen below its baseline band. A slow-query storm with healthy checkout (a heavy analytics batch, say) is recorded but not escalated. A checkout drop with a clean slow-query set points you away from the database and towards payment gateways, CDN, or front-end JavaScript, which is itself a useful negative result.

Worked example

A UK homeware brand runs Shopify for the storefront and a self-managed PostgreSQL 15 cluster behind a custom personalisation and inventory service that checkout calls synchronously. Snapshot taken on 14 Apr 26 at 20:05 BST, during the evening traffic peak. The card’s table for the 20:00 to 20:05 window: Six slow statements, all but one on the checkout path. Over the same window, Shopify checkout completion fell from a baseline of 71% to 38%. Both conditions are met, so the card fires red. The headline reads 6 slow queries during a checkout drop, with the FOR UPDATE row outlined as the worst offender. Three things are now obvious that neither team would have seen alone:
  1. The root cause is row-level lock contention on inventory_levels. The SELECT ... FOR UPDATE and the UPDATE on the same table are serialising every concurrent checkout for the same hot SKUs (a flash-sale item). At 600 calls in 5 minutes, backends are queueing on row locks; mean exec time of 1.8s is mostly lock wait, not work. Confirm with Idle-in-Transaction Backends and Connection Pool Saturation %.
  2. It is costing orders right now, not in theory. A 33-point drop in completion at peak is the difference between roughly £4,100 and £2,200 of orders for these 5 minutes. The owner does not need to read the query plan; they need to know whether to pull the flash-sale SKU from the homepage banner to relieve the contention while engineering fixes it.
  3. analytics-cron is a red herring. Its 260ms count(*) is slow but unrelated to the checkout path and ran 44 times, not 500. The card surfaces it for completeness, but the join makes clear it is not the driver.
The takeaways a team should remember:
  1. Co-occurrence is the whole point. Five slow queries during a flat checkout funnel is a tuning backlog item for next sprint. Five slow queries during a falling funnel is a page-someone-now event. This card refuses to conflate the two.
  2. Read the calling app column. It tells you instantly whether the slowdown is on the revenue path (checkout-svc) or a bystander (analytics-cron). Half of incident triage is deciding what to ignore.
  3. The mitigation and the fix are different jobs. Pulling the hot SKU off the banner (mitigation) stops the bleeding in 30 seconds; rewriting the reservation logic to use a skip-locked queue or a shorter transaction (fix) takes a sprint. Do the first now, schedule the second.

Sibling cards to reference together

Reconciling against the source

Where to look in PostgreSQL’s own tooling:
pg_stat_statements for the slow-statement set. Order by mean time over the window: SELECT queryid, calls, mean_exec_time, query FROM pg_stat_statements WHERE mean_exec_time > 100 ORDER BY mean_exec_time DESC LIMIT 20; Remember it accumulates since the last reset, so a single read shows lifetime averages, not the 5-minute view this card uses; take two snapshots a few minutes apart and diff the calls and total_exec_time columns to reconstruct the window. pg_stat_activity for what is running right now and why it is waiting: SELECT pid, state, wait_event_type, wait_event, now() - query_start AS runtime, query FROM pg_stat_activity WHERE state != 'idle' ORDER BY runtime DESC; A wait_event_type of Lock confirms the lock-contention story; IO points at disk or cache pressure instead. pg_locks joined to pg_stat_activity to find the blocking backend behind a FOR UPDATE pile-up. log_min_duration_statement in postgresql.conf to capture slow statements to the server log independently of pg_stat_statements.
On a managed service: the slow-statement set is the same, but the path differs. On Amazon RDS and Aurora, use Performance Insights (the “Top SQL” tab keyed by wait state) and the Enhanced Monitoring OS metrics; on Google Cloud SQL, the Query Insights panel; on Azure Database for PostgreSQL, Query Performance Insight. The checkout-completion half of this card has no PostgreSQL equivalent at all; reconcile that against the storefront platform’s own analytics (Shopify Analytics checkout funnel, BigCommerce Analytics, or the Adobe Commerce reports). Why our number may legitimately differ from a raw pg_stat_statements read: Cross-connector reconciliation:

Known limitations / FAQs

The card shows slow queries but checkout looks fine. Why no alert? Working as designed. The alert needs both halves: more than 5 slow statements and a checkout-completion drop in the same 5-minute bucket. A slow-query storm on a path that checkout does not touch (reporting, a background batch, an analytics cron) is recorded for context but not escalated, because it is not costing orders. Read Slow-Query Rate % for the path-agnostic view. Checkout dropped but this card stayed quiet. Doesn’t that mean the database was fine? Most likely yes, and that is a useful negative result. A clean slow-query set during a checkout drop steers you away from PostgreSQL and towards the payment gateway, CDN, DNS, or front-end JavaScript. Confirm with the storefront connector’s funnel and your payment provider’s status. The exception: if checkout reads come from a read replica or an application cache, the primary can be slow without checkout noticing until writes back up, so also glance at Replication Lag (seconds). Why 5 queries and not some other number? Five is the default co-occurrence floor that separates ordinary peak-traffic jitter (one or two queries occasionally crossing 100ms) from a genuine pile-up. It is an alert-threshold setting: tune it per profile in the Alert Rules tab. A high-throughput checkout path may want a higher floor; a low-volume B2B store where any contention is abnormal may want a lower one. My pg_stat_statements is not installed or not loaded. Does the card work? No. The database half of this card depends on the pg_stat_statements extension being in shared_preload_libraries and created with CREATE EXTENSION pg_stat_statements;. Without it there is no per-statement timing to read. On managed services it is usually enabled by default (RDS/Aurora ship it on); on a self-managed instance you must load it via the preload library list and restart. Until then the card returns no rows. Does this count queries against read replicas? By default the card reads the primary’s pg_stat_statements, since the checkout write path lands there. If your checkout reads are served from replicas and you want those statements included, point the connector at the replica’s stats as well; replicas maintain their own pg_stat_statements independently. Note that the two are not summed automatically; each instance is its own scope. The same query appears with different queryid values. Why? pg_stat_statements normalises constants but keys on the parse tree, so structurally different forms of “the same” query (a different number of IN list elements, a different join order produced by an ORM) get separate queryids. The card shows them as separate rows. If this fragments your view, look at the query text rather than counting rows, or raise pg_stat_statements.max so none are evicted. Can a single very slow query trip the alert? Not on its own; the count threshold is more than 5 distinct slow statements. One pathological 30-second query will show as a single row and, on its own, will not fire this card even if it tanks checkout. For single-query catastrophes lean on Query Latency p99 (ms) and Top 10 Slowest Queries, which are sensitive to one bad statement. This card is built for the many-queries-queueing-on-locks pattern that is typical at checkout. How do I stop the bleeding while engineering fixes the root cause? Two levers, both fast. First, on the storefront side, remove the hot SKU or promotion from prominent placement to reduce concurrent contention on the contended rows. Second, on the database side, identify and terminate any leaked idle in transaction backend holding the blocking lock (SELECT pg_terminate_backend(pid) against the offender from pg_stat_activity). The permanent fix (shorter transactions, skip-locked reservation queues, better indexing) is a separate, scheduled piece of work.

Tracked live in Vortex IQ Nerve Centre

Slow Queries During Checkout Window (5m) is one of hundreds of KPI pulses Vortex IQ tracks across PostgreSQL and 70+ other ecommerce connectors. Nerve Centre runs the detection layer; Vortex Mind investigates the cause when something moves; Ask Viq lets you interrogate any number in plain English. Start for free or book a demo to see this metric running on your own data.