DBAtlas · Database operations atlas

Catch plan regressions by queryid (pg_stat_statements snapshot comparison)

PostgreSQLPerformance

Command

-- 1) Take a reference snapshot (before deploy/upgrade/ANALYZE)
CREATE TABLE IF NOT EXISTS pgss_snapshot AS
  SELECT now() AS snap_time, userid, dbid, queryid, calls, total_exec_time,
         shared_blks_hit, shared_blks_read, left(query, 200) AS query
  FROM pg_stat_statements WITH NO DATA;
INSERT INTO pgss_snapshot
  SELECT now(), userid, dbid, queryid, calls, total_exec_time,
         shared_blks_hit, shared_blks_read, left(query, 200)
  FROM pg_stat_statements;

-- 2) Later: queries whose time per call grew the most since the snapshot
WITH base AS (
  SELECT DISTINCT ON (userid, dbid, queryid) *
  FROM pgss_snapshot
  ORDER BY userid, dbid, queryid, snap_time DESC
)
SELECT s.queryid,
       s.calls - b.calls AS new_calls,
       round((b.total_exec_time / b.calls)::numeric, 2) AS old_mean_ms,
       round(((s.total_exec_time - b.total_exec_time) / (s.calls - b.calls))::numeric, 2) AS new_mean_ms,
       round((b.shared_blks_hit + b.shared_blks_read)::numeric / b.calls, 0) AS old_blks_per_call,
       round((s.shared_blks_hit + s.shared_blks_read - b.shared_blks_hit - b.shared_blks_read)::numeric
             / (s.calls - b.calls), 0) AS new_blks_per_call,
       left(regexp_replace(s.query, '\s+', ' ', 'g'), 100) AS query
FROM pg_stat_statements s
JOIN base b USING (userid, dbid, queryid)
WHERE s.calls - b.calls >= 10
  AND b.calls > 0
ORDER BY ((s.total_exec_time - b.total_exec_time) / (s.calls - b.calls))
         / NULLIF(b.total_exec_time / b.calls, 0) DESC NULLS LAST
LIMIT 20;

-- 3) Is the suspicious queryid running right now?
SELECT pid, state, now() - query_start AS runtime, left(query, 100) AS query
FROM pg_stat_activity
WHERE query_id = -4094682686355101727;

What it does

Saves pg_stat_statements' cumulative counters to a table and compares them with the later state per queryid; shows the mean time and buffers per call of calls made after the snapshot next to the old values.

When to use it

To find the query whose plan changed after a deploy, major upgrade, statistics refresh or parameter change ('it was fast yesterday, slow today').

Caution

If pg_stat_statements_reset() runs between the snapshot and the comparison, or entries are evicted for lack of space (dealloc), the deltas are meaningless; check pg_stat_statements_info.stats_reset. An increase in buffers per call is a more reliable sign of a plan change than an increase in time, because it is independent of caching. To confirm the plan, EXPLAIN the query with real parameters or use auto_explain; PostgreSQL has no built-in plan pinning (the pg_hint_plan extension is required). Drop the snapshot table when done.

Open in DBAtlas →

Related