PostgreSQL 17
A reset call that now wipes everything
For anyone with a scheduled job that resets statistics on a cluster.
Reference page, revised in place. Last updated .
When refusing to run was the useful behaviour
The function that clears the cluster-wide statistics counters has always taken the name of what to clear. That design was deliberate: these counters are shared, one copy per cluster, and clearing them affects every dashboard and every rate computed from them, for everybody, with no undo. Requiring the caller to say which set they meant was a small piece of friction standing in front of a destructive operation.
PostgreSQL 17 removed the friction. A call with no argument now clears every shared statistics view the server has, and so does a call with an explicit null. The change is listed under Monitoring in the PostgreSQL 17 release notes, where it appears as an improvement in control, which it genuinely is: there was previously no single call that reset the lot, and a script that wanted one had to name each target and be updated whenever a new target appeared.
The awkward half is the second sentence of that change. A null argument used to be inert, and in a hand-written statement nobody writes a literal null, but a script assembling the call from a variable writes one every time the variable is unset.
The bare call on each server
SELECT pg_stat_reset_shared();
ERROR: function pg_stat_reset_shared() does not exist
LINE 1: SELECT pg_stat_reset_shared();
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
On 16 that is a parse-time refusal, which means a script containing it fails on its first run rather than on the run where it matters.
The null form on 16 is the interesting one, because it is accepted and it does nothing. The block below clears the I/O counters so there is a known timestamp, waits, then passes a null and asks whether anything moved.
SELECT pg_stat_reset_shared('io');
CREATE TABLE reset_watch AS SELECT max(stats_reset) AS reset_at FROM pg_stat_io;
DO $$
BEGIN
PERFORM pg_sleep(1);
PERFORM pg_stat_reset_shared(NULL);
END $$;
SELECT w.reset_at = (SELECT max(stats_reset) FROM pg_stat_io) AS timestamp_unchanged
FROM reset_watch w;
pg_stat_reset_shared
----------------------
(1 row)
SELECT 1
DO
timestamp_unchanged
---------------------
t
(1 row)
The timestamp did not move, so nothing was cleared. A nightly job doing that on 16 has been quietly doing nothing, possibly for years, and the report it feeds has been computing over a window that starts at the last real reset rather than at midnight.
On 17 the same job starts working, which sounds like good news and is the part to plan for. Here is the bare call against five shared views at once.
CREATE TABLE reset_watch AS
SELECT 'archiver' AS scope, stats_reset AS reset_at FROM pg_stat_archiver
UNION ALL SELECT 'bgwriter', stats_reset FROM pg_stat_bgwriter
UNION ALL SELECT 'checkpointer', stats_reset FROM pg_stat_checkpointer
UNION ALL SELECT 'io', max(stats_reset) FROM pg_stat_io
UNION ALL SELECT 'wal', stats_reset FROM pg_stat_wal;
DO $$
BEGIN
PERFORM pg_sleep(1);
PERFORM pg_stat_reset_shared();
END $$;
SELECT now_state.scope, prior.reset_at < now_state.reset_at AS cleared_by_the_bare_call
FROM (SELECT 'archiver' AS scope, stats_reset AS reset_at FROM pg_stat_archiver
UNION ALL SELECT 'bgwriter', stats_reset FROM pg_stat_bgwriter
UNION ALL SELECT 'checkpointer', stats_reset FROM pg_stat_checkpointer
UNION ALL SELECT 'io', max(stats_reset) FROM pg_stat_io
UNION ALL SELECT 'wal', stats_reset FROM pg_stat_wal) AS now_state
JOIN reset_watch AS prior USING (scope)
ORDER BY scope;
SELECT 5
DO
scope | cleared_by_the_bare_call
--------------+--------------------------
archiver | t
bgwriter | t
checkpointer | t
io | t
wal | t
(5 rows)
Five views, one call, no argument and no confirmation. Recovery prefetching and the cache counters go with them.
The list of targets, which also grew
The other way to find out what a version accepts is to give it something it does not, because this function validates its argument and says so.
SELECT pg_stat_reset_shared('bogus');
ERROR: unrecognized reset target: "bogus"
HINT: Target must be "archiver", "bgwriter", "io", "recovery_prefetch", or "wal".
SELECT pg_stat_reset_shared('bogus');
ERROR: unrecognized reset target: "bogus"
HINT: Target must be "archiver", "bgwriter", "checkpointer", "io", "recovery_prefetch", "slru", or "wal".
Two targets appear on 17 that are not on 16. One of them is the checkpointer, which is a consequence of the counters having moved into a view of their own, and it is the target a script that resets the background writer now needs to name as well if it wants the same effect it used to get from one call.
It is worth noticing that this function checks its argument and raises an error on a bad one. The neighbouring function for the cache counters does not, and hands an unrecognised name to a catch-all bucket instead. Two functions, two behaviours, one release.
Where this bites in practice
The risk is not somebody typing the bare call at a prompt. It is the three places a statistics reset already lives in most fleets.
- A reporting job that resets a named target on a schedule. Check what it names on 17, because the set it used to cover has changed shape underneath it.
- A script that builds the call from a variable or a parameter. An unset variable that produced a harmless no-op on 16 produces a cluster-wide reset on 17.
- A runbook step written as “reset the stats before starting the test”. On 16 that instruction was ambiguous and mostly harmless; on 17 the shortest reading of it destroys every baseline on the cluster.
The counters themselves are not data, and losing them costs nobody an outage. What it costs is every rate computed over a window that spans the reset, which will read as a step change in whichever direction the graph happens to be pointing, and the person investigating that will not be the person who ran the job.
What a reset costs to run, and to undo
Running it is free and instantaneous. It requires no restart, no setting and no lock, and it takes effect for every session immediately.
Undoing it is impossible. There is no snapshot and no history inside the server; the previous values existed only in whatever external system was scraping them. That asymmetry is the whole argument for treating a statistics reset as a change that somebody approves rather than a step in a script that runs at two in the morning.