At a glance
PostgreSQL QPS Spike vs Ecom Order Rate plots database query throughput against the storefront’s order rate on a dual axis over a rolling 15-minute window. In a healthy storefront the two lines move together: more shoppers means more queries and more orders. When query volume spikes but orders do not, the extra load is not coming from buyers. It is bots, scrapers, a misbehaving integration, or a runaway query loop hammering the database for no commercial return. This card exists to separate “we are busy because we are selling” from “we are busy because something is abusing us”, which are the same load on the database but opposite situations for the business.
| PostgreSQL side | Queries per second, the live throughput from the same basis as the Queries per Second (live) card (transaction/statement rate derived from pg_stat_database counters or pg_stat_statements call deltas). |
| Ecom side | The order rate from the connected storefront connector (orders per interval from Shopify, BigCommerce, or Adobe Commerce), windowed to the same 15-minute buckets. |
| What the card shows | A dual-axis chart, QPS on one axis and order rate on the other, so divergence between the two lines is immediately visible. |
| Aggregation window | 15-minute rolling window, refreshed every cycle. Short enough to catch a scraper burst, long enough to ignore single-request noise. |
| Time window | 15m (rolling). |
| Alert trigger | A QPS spike with no corresponding order spike, the bot / scraper signature. Query volume jumps while the order line stays flat. |
| Roles | DBA, platform engineering, SRE, ecommerce operations, security. |
Calculation
The PostgreSQL side reads query throughput on the same basis as the standalone QPS card: the delta in transaction and statement counters frompg_stat_database (xact_commit + xact_rollback) or, where pg_stat_statements is enabled, the change in total calls across the window, divided by elapsed seconds. The result is queries (or transactions) per second over each 15-minute bucket.
The ecom side reads the order rate from the linked storefront connector, counting orders created per bucket and aligning them to the same time grid. The card then watches the relationship between the two series:
pg_stat_activity, and the top-queries card.
Worked example
A platform team runs a storefront on Adobe Commerce backed by PostgreSQL. Baseline daytime load is roughly 800 QPS and 40 orders per 15-minute bucket. On 21 Apr 26 from 16:00 BST the card shows the lines pulling apart:| Bucket (BST) | QPS (avg) | Orders / 15m | Reading |
|---|---|---|---|
| 15:45 | 820 | 41 | Baseline, lines tracking |
| 16:00 | 1,950 | 39 | QPS up 2.4x, orders flat |
| 16:15 | 2,480 | 42 | QPS up 3x, orders flat |
| 16:30 | 2,510 | 38 | Sustained, orders flat |
- This is not a sale. A genuine traffic surge lifts both lines. Orders are sitting dead flat at their normal 38 to 42 while QPS has tripled. The extra ~1,700 QPS is buying the business nothing.
- The shape points at reads, not writes. Cross-checking Top 10 Slowest Queries and
pg_stat_activityshows the surge is almost entirely catalogueSELECTs from a narrow set of client IPs, with no matching rise in order-insert statements. That is the classic scraper fingerprint: crawling product pages, never checking out.
- Load on the database is not the same as value to the business. QPS measures work done; the order rate measures work that pays. When they diverge, the gap is waste, and waste that degrades latency is waste that costs real sales.
- Both-lines-up is good news; one-line-up is the alarm. Scale happily to a spike that lifts orders too. Investigate a spike that does not, because the database is being used by something other than buyers.
- The card flags the pattern; the next click finds the culprit. Once divergence is confirmed, jump to the top-queries card and
pg_stat_activityto identify which statements and which clients are driving the non-commercial load, then act at the edge.
Sibling cards
| Card | Why pair it with this card | What the combination tells you |
|---|---|---|
| Queries per Second (live) | The standalone QPS line without the order overlay. | This card adds the commercial context that tells you whether the QPS is worth anything. |
| PgBouncer Pool Saturation vs Traffic Burst | The capacity cross-channel sibling. | A non-commercial QPS spike often saturates the pool, degrading real shoppers; read the two together. |
| Top 10 Slowest Queries | The query-level drill-down. | Once divergence is flagged, this names the statements driving the surge. |
| Buffer Cache Hit Rate % | The cache-pressure view. | A scraper pulling cold catalogue pages drives the hit rate down, confirming non-commercial reads. |
| Query Latency p95 (ms) | The shopper-latency impact. | The cost of a non-commercial spike shows up as latency on real queries. |
| Slow Queries During Checkout Window (5m) | The checkout-specific latency cross-channel card. | Confirms whether the spike is slowing the queries that actually convert. |
| PostgreSQL Health Score | The executive composite. | A sustained non-commercial spike degrades latency and cache, pulling the composite down. |
Reconciling against the source
Where to look directly:For the PostgreSQL side, sample throughput twice and difference it:Why our number may legitimately differ from a manual sample:SELECT sum(xact_commit + xact_rollback) FROM pg_stat_database;a few seconds apart gives transactions per second, or usepg_stat_statements(SELECT sum(calls) FROM pg_stat_statements;) deltas for statement-level QPS. Inspect live load withSELECT state, query, client_addr FROM pg_stat_activity WHERE state = 'active';to see what is running and from where during a spike. For the order side, reconcile against the storefront connector’s order count for the same 15-minute window (Shopify, BigCommerce, or Adobe Commerce order reports).
| Reason | Direction | Why |
|---|---|---|
| Counter basis | Variable | Transactions per second (pg_stat_database) and statements per second (pg_stat_statements) are different numbers; one transaction can contain many statements. The card states which basis it uses; match it when reconciling. |
| Window smoothing | Lower peaks | The card reports the 15-minute bucket average; a hand sample taken at the peak instant reads higher than the bucketed value. |
| Counter reset | Lower | pg_stat_statements and pg_stat_database counters reset on pg_stat_reset() or instance restart; a recent reset shortens the delta baseline. |
| Order-side timing | Marginal | Order creation timestamps and database samples come from different systems bucketed to the same grid; a small phase offset between the two lines is normal. |
Queries, CommitThroughput, Transactions) and detailed query mix via Performance Insights; Cloud SQL surfaces query and transaction counts in Cloud Monitoring and Query Insights; Azure Database for PostgreSQL exposes tps and per-query stats. Use the provider metric to corroborate the QPS line, and the storefront admin to corroborate the order line; the divergence between them is the card’s actual output.
Known limitations / FAQs
QPS spiked and orders also rose. Should I worry? No, that is healthy demand. When both lines lift together the database is busy because the business is busy; the correct response is to make sure you have capacity (check the pool-saturation and latency cards) and let it run. The alert is specifically for QPS rising while orders stay flat. QPS spiked, orders flat. How do I find the culprit? The card confirms the pattern; the next clicks identify the source. Open Top 10 Slowest Queries and runSELECT query, client_addr, count(*) FROM pg_stat_activity WHERE state='active' GROUP BY query, client_addr ORDER BY count(*) DESC; to see which statements and which client IPs dominate the surge. A narrow set of IPs hammering catalogue reads with no order inserts is the scraper signature; act at the edge with a WAF or rate limit.
Could a flat order line with high QPS be legitimate?
Occasionally. A scheduled analytics export, a cache-warming job, a full-catalogue reindex, or a heavy reporting query against production all raise QPS without orders and are not malicious. The card flags the pattern; you decide whether the load is wanted. Move such jobs to a read replica so they stop competing with shopper traffic and stop tripping the alert.
Why a 15-minute window rather than real-time?
A 15-minute bucket is short enough to catch a scraper burst within a refresh or two, but long enough to ignore the per-second jitter that would make a real-time comparison noisy. Order rates are naturally lumpy at the second level, so a sensible bucket is needed to compare the two series fairly.
The QPS number does not match my managed-service console. Why?
Usually a basis mismatch. The card may report transactions per second while the console reports statements (or vice versa), and one transaction can hold many statements. Window smoothing also lowers the card’s value relative to an instantaneous console peak. Confirm both are measuring the same thing over the same window before treating it as a discrepancy.
Does this card detect a DDoS or only scrapers?
It detects any non-commercial load that reaches the database: aggressive scrapers, price bots, a broken client in a retry loop, or the database-facing portion of an application-layer attack. It does not see traffic blocked at the edge before it hits PostgreSQL, and it is not a security product. Treat it as an early signal that warrants a look at edge logs and WAF rules, then pair it with PgBouncer Pool Saturation vs Traffic Burst to gauge the impact on real shoppers.