Skip to content

Regressions and applied fixes

With pg_stat_statements available, PgQueryNarrative can notice a query getting worse over time and turn that into an investigation. This page covers detection through to the system confirming, or contradicting, a fix.

Snapshot collection and baseline

A background poller (REGRESSION_POLLER_ENABLED and SECURITY_STAT_STATEMENTS_ENABLED, both true by default when pg_stat_statements is enabled) runs every REGRESSION_POLLER_INTERVAL (default 15m), once per organization × connection that has the stats permission on that connection. It takes a PostgreSQL advisory lock per (org, connection) so replicas of the server don't double-poll; there is no separate leader election.

It skips a connection whose read-only role is shared by more than one organization (logging one line): pg_stat_statements is kept per role, so polling would copy every organization's SQL text into each organization's own tables. Give each organization its own read-only credentials (/admin/connection-secrets) to poll it.

Each poll snapshots the top 50 statements by total_time (excluding the tool's own traffic), computes the delta since the previous interval for each queryid, and drops intervals with no prior snapshot or a negative delta (a stats counter reset). The baseline for a query is the median interval mean latency, the p90 interval total time, and the average calls and rows across its recorded intervals. A query needs at least 3 baseline intervals before it is eligible to alert.

Alert rules

An alert fires when, relative to baseline:

  • mean latency is up by at least REGRESSION_MEAN_THRESHOLD_PCT (default 50%), or
  • total time is up by at least 1.5× that threshold, or
  • call volume is up by at least 200%, or
  • rows returned per call changed by at least 80%

Impact tier: critical at ≥ REGRESSION_CRITICAL_THRESHOLD_PCT (200), high at ≥ REGRESSION_HIGH_THRESHOLD_PCT (100), otherwise medium. There is at most one open alert per (organization, connection, queryid); a second detection updates the existing alert rather than creating a duplicate. An alert auto-resolves once the mean change drops back to half the threshold, and acknowledgement is cleared automatically if impact later escalates.

From alert to investigation

curl -s -X POST http://localhost:8080/api/v1/investigations/from-regression \
  -d '{"regression_alert_id": "..."}'

This looks up the most recent snapshot SQL for that queryid on that connection and opens (or reuses, if one already exists) an investigation with the alert linked. It rejects a snapshot whose SQL text is empty or was truncated by pg_stat_statements.track settings. From there the flow is the same as any other investigation: Investigate a slow query.

GET /workspace/regressions lists the inbox; POST /workspace/regressions/{id}/acknowledge marks one seen. On the default make demo seed, the inbox is empty unless real pg_stat_statements activity exists; set APP_ENV=demo for seeded demo alerts, and never in an environment where the numbers are acted on.

Fix lifecycle

stateDiagram-v2
  [*] --> proposed
  proposed --> verified
  proposed --> abandoned
  verified --> applied
  verified --> proposed
  verified --> abandoned
  applied --> confirmed: poller, mean ≤ 0.80 × baseline
  applied --> regressed: poller, >72h and mean ≥ 0.95 × baseline
  applied --> abandoned
  confirmed --> applied
  regressed --> applied
  regressed --> abandoned

POST /investigations/{id}/fix moves a fix between proposed, verified, applied and abandoned. confirmed and regressed cannot be set through the API: they are measurements, written only by the poller, and only for investigations linked to a regression alert. Moving a fix to applied snapshots a baseline for later comparison: the latest interval mean for the linked query, or the raw snapshot mean if no interval baseline exists yet.

After that, on each poll the poller checks that connection's fixes marked applied: mean latency at or below 80% of the applied-time baseline moves the fix to confirmed; more than 72 hours since apply with the mean still at or above 95% of baseline moves it to regressed. A fix applied to an investigation with no linked regression alert stays applied indefinitely; there is no measurement path for it.

See also

Architecture: regression detection · Investigate a slow query · Multiple connections