At a glance
This card reports how many SQL statements your Supabase Postgres database is executing per second, live. Where the PostgREST request rate measures how many API calls arrive, this measures the database work those calls (plus Edge Functions, scheduled jobs, Auth, and anything else with a connection) actually generate. It is the single best headline for “how hard is the database working right now?”. For a platform or SRE team it is the load gauge that sits beneath every latency, cache, and capacity card: a latency change is a regression or a load story depending on whether QPS moved with it. It carries no fixed alert because raw throughput is neither healthy nor unhealthy on its own; it is the denominator that makes the rest of the connector interpretable.
| What it tracks | The rate at which Postgres executes SQL statements, in queries per second, for the live window. Counts all statements across all clients (PostgREST, Edge Functions, cron jobs, Auth, direct connections). |
| Data source | Postgres cumulative statement counters: the xact_commit + xact_rollback and tup_* activity in pg_stat_database, combined with pg_stat_statements calls. The card reads the delta between polls and divides by the elapsed time to produce a live rate. |
| Time window | RT (live, refreshed continuously). |
| Alert trigger | None by default. QPS is load context, not a pass/fail signal. Add a custom ceiling or floor in the Sensitivity tab to catch a runaway surge or a drop to zero. |
| Roles | dba, platform, sre |
Calculation
QPS is a delta of cumulative counters divided by elapsed time:pg_stat_database (xact_commit, xact_rollback) and per-statement call counts in pg_stat_statements (calls). The lifetime totals are dominated by history and barely move, so the card takes the difference between two polls and divides by the time between them. That delta is what makes the reading “live”: a fresh surge or a sudden idle shows up promptly rather than being buried under months of accumulated counts.
Two points shape how to read it:
- This is broader than the API. PostgREST is one source of queries, but Edge Functions, scheduled jobs (pg_cron), the Auth service, Storage metadata, and any direct database connection all execute statements that count here. That is why database QPS is usually higher than PostgREST Request Rate (req/sec), and why a QPS spike with a flat API request rate points at a non-API source: a cron job, a migration, or a background worker.
- The ratio to API requests is diagnostic. Divide database QPS by PostgREST request rate to get queries-per-request. A creeping ratio is the classic signature of an N+1 pattern, where one API call quietly fans out into many database round trips (embedded resources, RLS policies that query other tables, app code looping over rows). Watching the ratio, not just the absolute QPS, catches efficiency regressions early.
Worked example
A platform team runs a Supabase project for a SaaS product. Database QPS normally tracks the API request rate at roughly 4 queries per request and peaks around 1,200 QPS. Snapshot taken on 05 May 26 at 10:50 BST after the on-call dashboard flagged an unusual climb.| Window | DB QPS | PostgREST req/sec | Queries/request | Cache hit rate | State |
|---|---|---|---|---|---|
| 10:00 (baseline) | 760 | 190 | 4.0 | 99.3% | healthy |
| 10:45 | 2,400 | 205 | 11.7 | 97.1% | elevated |
| 10:50 | 2,950 | 210 | 14.0 | 95.8% | degrading |
- QPS nearly quadrupled while the API request rate barely moved. PostgREST went 190 to 210 req/sec (normal) but database QPS went 760 to 2,950. The extra work is not coming from more API calls; each call is now generating far more queries. The queries-per-request ratio jumped from 4.0 to 14.0.
- That ratio is the smoking gun. A request that used to make 4 queries now makes 14. That is the textbook signature of an N+1 regression: a recent change introduced a loop that fetches related rows one at a time instead of in a single joined query. Cross-referencing PostgREST API Latency p95 (ms), latency has started creeping up as the extra round trips add overhead and the cache hit rate slips.
- It is a code regression, not a load event. Because the API request rate is flat, scaling the tier or the pool would only paper over it. The fix is to collapse the N+1 back into a single query (a PostgREST embedded select, or a join), which restores both QPS and latency.
pg_stat_statements (a low-cost query whose calls had exploded), shipped a fix that fetched the related rows in one embedded select, and QPS dropped back to its normal ratio while latency recovered. The absolute QPS number was the alarm; the queries-per-request ratio was the diagnosis.
Three takeaways:
- Database QPS is broader than API load. It counts every statement from every connection: API, Edge Functions, cron, Auth, direct clients. A QPS spike with flat API traffic points at a non-API source.
- Watch the ratio to request rate, not just the number. Queries-per-request rising at constant API load is the cleanest early sign of an N+1 or chatty-endpoint regression, long before latency makes it obvious.
- Scaling does not fix a code regression. If the extra QPS comes from more queries per request rather than more requests, a bigger tier just delays the problem. Collapse the fan-out at the source.
Sibling cards
| Card | Why pair it with Database Queries per Second | What the combination tells you |
|---|---|---|
| PostgREST Request Rate (req/sec) | The API-side denominator for queries-per-request. | QPS rising faster than request rate equals an N+1 or chatty-endpoint regression. |
| PostgREST API Latency p95 (ms) | Latency interpreted against database load. | Latency up with QPS up equals capacity; latency up with QPS flat equals a query regression. |
| Buffer Cache Hit Rate % | More queries can push the working set past cache. | QPS up and hit rate down equals load outgrowing memory or a new scan-heavy query. |
| Supavisor Pool Saturation % | High QPS pressures the connection pool. | Rising QPS driving saturation toward 90% predicts the next latency breach. |
| Slow-Query Rate % | Distinguishes more queries from slower queries. | QPS flat but slow-query rate up equals a plan regression, not a load change. |
| Database Query Error Rate % | Counts how much of the QPS is failing. | QPS steady with error rate climbing equals the same load failing more often. |
| Supabase QPS Spike vs Ecom Order Rate | Ties QPS to downstream business activity. | A QPS spike with no order spike equals bots, retries, or a runaway job, not real demand. |
| Supabase Health Score | The composite that reads load against capacity. | Sustained QPS beyond safe capacity erodes the composite before any single gauge breaches. |
Reconciling against the source
Where to look in Supabase’s own tooling:In the Supabase dashboard, open Reports → Database for the query-throughput and transaction charts. The dashboard load charts and this card draw on the same Postgres activity counters. Compute it yourself from the system catalogs:Why our number may legitimately differ from Supabase’s own view:SELECT xact_commit + xact_rollback AS txns FROM pg_stat_database WHERE datname = current_database();sampled twice and differenced gives transactions per second;pg_stat_statementssum(calls)differenced gives statements per second (the closer match to QPS). To attribute the QPS, orderpg_stat_statementsbycallsto see which statements dominate, and checkpg_stat_activityfor what is currently running and from which application. The managed-service console exposes equivalent throughput charts under the project’s observability section; confirm the chart is counting statements (not transactions or connections) over a matching interval before comparing.
| Reason | Direction | Why |
|---|---|---|
| Statements vs transactions | Card may read higher | The card counts SQL statements; a chart counting transactions (xact_commit + xact_rollback) reads lower because one transaction can contain many statements. |
| Delta vs lifetime | Card more responsive | The card uses the between-poll delta; a hand-run pg_stat_database snapshot shows lifetime totals that barely move and hide a fresh surge. |
| pg_stat_statements scope | Possible undercount | pg_stat_statements tracks normalised statements up to its configured limit; very high-cardinality query text can be capped, slightly under-counting. |
| Poll interval | Variable | A short poll interval surfaces instantaneous spikes that a longer dashboard bucket averages away. |
| Stats reset | Temporary mismatch | pg_stat_reset() zeroes the counters; the card’s delta is unaffected but a lifetime-based source query restarts from zero. |
Known limitations / FAQs
Why does this card have no alert? Because raw QPS is neither healthy nor unhealthy on its own: 3,000 QPS is excellent on a well-provisioned project and a crisis on a tiny one. The card stays alert-free so it can act as neutral load context for the latency, cache, and capacity gauges, which is where the pass/fail judgement lives. If you want to catch a runaway surge or a drop to zero, add a custom ceiling or floor in the Sensitivity tab tuned to your project’s real pattern. My QPS spiked but my PostgREST request rate did not. What happened? Something other than the API is generating queries. The common sources are a scheduled job (pg_cron), a migration or backfill, a background worker, an Auth-heavy event, or a direct database connection from a script. Orderpg_stat_statements by calls to see which statement jumped, and check pg_stat_activity for the application_name behind it. A QPS spike at flat API traffic almost always traces to a non-API client.
Database QPS is much higher than my API request rate. Is that a problem?
Not inherently, one API call legitimately makes several queries. It becomes a problem when the ratio rises over time at constant API load, which signals an N+1 pattern: an endpoint quietly fanning out into more and more queries (embedded resources, RLS policies that query other tables, app loops). Track queries-per-request, not just absolute QPS, and investigate when the ratio climbs.
Does this count queries from Edge Functions and cron jobs?
Yes. The card counts statements from every connection to Postgres: PostgREST, Edge Functions, pg_cron, the Auth service, Storage metadata, and any direct client. That breadth is the point, it is the true database load, not just the API slice. To isolate the API portion, compare against PostgREST Request Rate (req/sec).
Why is the card’s QPS different from my pg_stat_database query?
Most likely you are comparing statements with transactions. pg_stat_database exposes transaction counts (xact_commit + xact_rollback); the card counts statements, and one transaction can hold many statements, so the card reads higher. Also, a hand-run query on pg_stat_database shows lifetime totals since the last reset, which move slowly, whereas the card uses the live delta. For a like-for-like statement rate, difference sum(calls) from pg_stat_statements across two samples.
My QPS dropped to near zero. Is the database down?
Possibly, but check the benign causes first. A drop to zero can mean the application stopped sending traffic (a broken deploy, a gateway dropping requests upstream), a maintenance window, or, on a Free-tier project, the project pausing after inactivity. Confirm with Project Uptime and PostgREST Request Rate (req/sec) before assuming a database outage.
Can high QPS by itself cause problems even if every query is fast?
Yes. Very high QPS pressures connections (each query needs one), CPU, and the cache even when individual statements are cheap. Watch Supavisor Pool Saturation % and Buffer Cache Hit Rate % alongside QPS; a sustained climb toward your tier’s limits is the signal to optimise the chattiest endpoints or move up a tier before latency breaks.