Skip to main content
raphi's corner
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

pgstat_snap

Purpose of this script

The cumulative statistics system (CSS) in PostgreSQL and pg_stat_statements in particular lack any timing information, all values are cumulative and the only way to figure out the difference between query executions is to reset the stats every time or work with averages.

With the pgstat_snap extension, you can create timestamped snapshots of pg_stat_statements and pg_stat_activity when needed. It also provides views that show the difference between every snapshot for every query and database.

Requirements

pg_stat_statements must be loaded and tracking activated in the postgres config:

shared_preload_libraries = 'pg_stat_statements'

Recommended settings:

pg_stat_statements.track = all  
pg_stat_statements.track_utility = off

The extension has to be created in the database in which pgstat_snap will be installed:

create extension pg_stat_statements;

Installation

To install the extension, download these files:

pgstat_snap--1.0.sql
pgstat_snap.control

And copy them to the extension directory of PostgreSQL

sudo cp pgstat_snap* $(pg_config --sharedir)/extension/

You can then install the extension in any database that has the pg_stat_statements extension enabled, superuser right are NOT needed:

create extension pgstat_snap;

It can also be installed into a different schema but be sure to have it included in the search_path:

create extension pgstat_snap schema my_schema;

This will create the following tables and views:

  pgstat_snap_stat_history   -> pg_stat_statements history (complete snapshot)
  pgstat_snap_act_history    -> pg_stat_activity history (complete snapshot)
  pgstat_snap_diff_all       -> view containing the sum and difference of each statement between snapshots
  pgstat_snap_diff           -> view containing only the difference of each statement between snapshots

Usage

Start gathering snapshots with, e.g. every 1 second 60 times:

CALL pgstat_snap_collect(1, 60);

Or gather a snapshot every 5 seconds for 10 minutes:

CALL pgstat_snap_collect(5, 120);

IMPORTANT: on very busy clusters with many databases a lot of data can be collected, 500mb per minute or more. Don’t let it run for a very long time with short intervals, unless you have the disk space for it.

Reset

Because everything is timestamped, a reset is usually not needed between CALLs to create_snapshot. But you can to cleanup and keep the tables smaller. You can also reset pg_stats*.

Reset all pgstat_snap tables with:

  SELECT pgstat_snap_reset();   -> reset only pgstat_snap.pgstat*history tables
  SELECT pgstat_snap_reset(1);  -> also select pg_stat_statements_reset()
  SELECT pgstat_snap_reset(2);  -> also select pg_stat_reset()

How it works

The first argument to create_snapshot is the interval in seconds, the second argument is how many snapshots should be collected. Every interval seconds, select * from pg_stat_statements will be inserted into pgstat_snap_stat_history and select * from pgstat_act_statements into pgstat_snap_act_history.

For every row, a timestamp will be added. Only rows where the “rows” column has changed will be inserted into pgstat_snap_stat_history and always only one row per timestamp, dbid and queryid. Every insert is immediately committed to be able to live follow the tables/views.

The views have a _d column which displays the difference between the current row and the last row where the query was recorded in the pgstat_snap_stat_history table. NULL values in rows_d, calls_d and so on mean, that no previous row for this query was found because it was executed the first time since create_snapshot was running.

The views also contain the datname, wait events and the first 20 characters of the query, making it easier to identify queries of interest.

Uninstall

To completely uninstall pgstat_snap, run:

DROP EXTENSION pgstat_snap;

Views description

pgstat_snap_diff

This view only contains the difference between the previous and next execution of a queryid/dbid pair:

Column NameDescription
snapshot_timeTimestamp
queryidQuery ID
queryQuery Text (first 20 characters)
datnameDatabase Name
usenameUsername
wait_event_typeEvent Type - NULL if no wait occurred
wait_eventWait Event - NULL if no wait occurred
rows_dDifference in rows from the previous snapshot_time
calls_dDifference in calls from the previous snapshot_time
exec_ms_dDifference in total execution time from the previous snapshot_time
sb_hit_dDifference in shared block hits from the previous snapshot_time
sb_read_dDifference in shared block reads from the previous snapshot_time
sb_dirt_dDifference in shared blocks dirtied from the previous snapshot_time
sb_write_dDifference in shared blocks written from the previous snapshot_time

Sample output

select * from pgstat_snap_diff order by 1;
snapshot_timequeryidquerydatnameusenamewait_event_typewait_eventrows_dcalls_dexec_ms_dsb_hit_dsb_read_dsb_dirt_dsb_write_d
2025-03-25 11:00:194380144606300689468UPDATE pgbench_tellpostgrespostgresLocktransactionid44854485986.26209822827000
2025-03-25 11:00:204380144606300689468UPDATE pgbench_tellpostgrespostgresLocktransactionid12041204228.8224136115000
2025-03-25 11:00:207073332947325598809UPDATE pgbench_branpostgrespostgresLocktransactionid120412041758.1904995655000
2025-03-25 11:00:217073332947325598809UPDATE pgbench_branpostgrespostgresLocktransactionid127312732009.2275756024000
2025-03-25 11:00:222931033680287349001UPDATE pgbench_accopostgrespostgresClientClientRead937793771818.464415661213699735835
2025-03-25 11:00:227073332947325598809UPDATE pgbench_branpostgrespostgresLocktransactionid135613561659.8068566341000
2025-03-25 11:00:237073332947325598809UPDATE pgbench_branpostgrespostgresLocktransactionid116811681697.3228745484000
2025-03-25 11:00:247073332947325598809UPDATE pgbench_branpostgrespostgres[NULL][NULL]113511351539.9996185237000
2025-03-25 11:00:245744520630148654507SELECT abalance FROMpostgrespostgres[NULL][NULL]1167911679114.34751449451000
2025-03-25 11:00:257073332947325598809UPDATE pgbench_branpostgrespostgresLocktransactionid127412741861.7477336043000
2025-03-25 11:00:267073332947325598809UPDATE pgbench_branpostgrespostgresLocktransactionid108010801855.8036605080000
2025-03-25 11:00:277073332947325598809UPDATE pgbench_branpostgrespostgresLocktransactionid115511551669.4996085373000
2025-03-25 11:00:277113545590461720994SELECT CASE WHEN pgpostgrespostgresClientClientRead110.0987830000
2025-03-25 11:00:287073332947325598809UPDATE pgbench_branpostgrespostgresLocktransactionid117811781623.3654925466000
2025-03-25 11:00:292931033680287349001UPDATE pgbench_accopostgrespostgresClientClientRead835083501456.806356584003317613530

pgstat_snap_diff_all

This view contains the difference between the previous and next execution of a queryid/dbid pair and the sum of the fields as recorded in pg_stat_statements at that time:

Column NameDescription
snapshot_timeTimestamp
queryidQuery ID
queryQuery Text (first 20 characters)
datnameDatabase Name
usenameUsername
wait_event_typeEvent Type - NULL if no wait occurred
wait_eventWait Event - NULL if no wait occurred
rowsValue of rows at this time in pg_stat_statements
rows_dDifference in rows from the previous snapshot_time
callsValue of calls at this time in pg_stat_statements
calls_dDifference in calls from the previous snapshot_time
exec_msValue of total execution time at this time in pg_stat_statements
exec_ms_dDifference in total execution time from the previous snapshot_time
sb_hitValue of shared block hits at this time in pg_stat_statements
sb_hit_dDifference in shared block hits from the previous snapshot_time
sb_readValue of shared block reads at this time in pg_stat_statements
sb_read_dDifference in shared block reads from the previous snapshot_time
sb_dirtValue of shared blocks dirtied at this time in pg_stat_statements
sb_dirt_dDifference in shared blocks dirtied from the previous snapshot_time
sb_writeValue of shared blocks written at this time in pg_stat_statements
sb_write_dDifference in shared blocks written from the previous snapshot_time

Sample output

select * from pgstat_snap_diff_all order by 1;
snapshot_timequeryidquerydatnameusenamewait_event_typewait_eventrowsrows_dcallscalls_dexec_msexec_ms_dsb_hitsb_hit_dsb_readsb_read_dsb_dirtsb_dirt_dsb_writesb_write_d
2025-03-25 11:00:194380144606300689468UPDATE pgbench_tellpostgrespostgresLocktransactionid2418184485241818448539693.679945986.2620981237102123709840900220
2025-03-25 11:00:204380144606300689468UPDATE pgbench_tellpostgrespostgresLocktransactionid2430221204243022120439922.502358228.8224131243217124321340900220
2025-03-25 11:00:207073332947325598809UPDATE pgbench_branpostgrespostgresLocktransactionid24302012042430201204322868.3460021758.1904991142441114244010710210
2025-03-25 11:00:217073332947325598809UPDATE pgbench_branpostgrespostgresLocktransactionid24429312732442931273324877.5735772009.2275751148465114846410710210
2025-03-25 11:00:222931033680287349001UPDATE pgbench_accopostgrespostgresClientClientRead2456529377245652937751188.1723941818.4644152140583202221512206736992331287358215035
2025-03-25 11:00:227073332947325598809UPDATE pgbench_branpostgrespostgresLocktransactionid24564913562456491356326537.3804331659.8068561154806115480510710210
2025-03-25 11:00:237073332947325598809UPDATE pgbench_branpostgrespostgresLocktransactionid24681711682468171168328234.7033071697.3228741160290116028910710210
2025-03-25 11:00:247073332947325598809UPDATE pgbench_branpostgrespostgres[NULL][NULL]24795211352479521135329774.7029251539.9996181165527116552610710210
2025-03-25 11:00:245744520630148654507SELECT abalance FROMpostgrespostgres[NULL][NULL]24795411679247954116792917.940273114.34751411217491121749000000
2025-03-25 11:00:257073332947325598809UPDATE pgbench_branpostgrespostgresLocktransactionid24922612742492261274331636.4506581861.7477331171570117156910710210
2025-03-25 11:00:267073332947325598809UPDATE pgbench_branpostgrespostgresLocktransactionid25030610802503061080333492.2543181855.8036601176650117664910710210
2025-03-25 11:00:277073332947325598809UPDATE pgbench_branpostgrespostgresLocktransactionid25146111552514611155335161.7539261669.4996081182023118202210710210
2025-03-25 11:00:277113545590461720994SELECT CASE WHEN pgpostgrespostgresClientClientRead3783137831373.3988460.09878300000000
2025-03-25 11:00:287073332947325598809UPDATE pgbench_branpostgrespostgresLocktransactionid25263911782526391178336785.1194181623.3654921187489118748810710210
2025-03-25 11:00:292931033680287349001UPDATE pgbench_accopostgrespostgresClientClientRead2540028350254002835052644.9787501456.8063562198983207691612538433172392636135218030

Query Examples

Depending on screensize, you might want to set format to aligned, especially when querying pstat_snap_diff_all:

\pset format aligned

What was happening:

select * from pgstat_snap_diff order by 1;

What was every query doing:

select * from pgstat_snap_diff order by 2,1;

Which database touched the most rows:

select sum(rows_d),datname from pgstat_snap_diff group by datname;

Which query DML affected the most rows:

select sum(rows_d),queryid,query from pgstat_snap_diff where upper(query) not like 'SELECT%' group by queryid,query;

What wait events happened which weren’t of type Client:

select * from pgstat_snap_diff where wait_event_type is not null and wait_event_type <> 'Client' order by 2,1;

If needed you can access all columns for a particular query directly in the history tables:

select * from pgstat_snap_stat_history where queryid='123455678909876';