DB Internals AtlasPostgreSQL 18.6 Home About

Background processes

Cumulative statistics

Every process counts what it does (rows read, rows changed, blocks read, checkpoints …) and from time to time adds its counts to a shared-memory statistics area that the pg_stat_* views read. Since PostgreSQL 15 there is no separate statistics collector process.

In plain words

PostgreSQL keeps running totals of what happens: how many rows each table returned, inserted, updated and deleted, how many blocks were read from disk or found in cache, how many transactions committed, how often the checkpointer ran. You read them in the pg_stat_* views. Autovacuum also uses them to decide which tables need cleaning.

Counting must be cheap, so each process first accumulates many cumulative counters locally and periodically flushes them to the shared statistics area, normally just before going idle and not more often than about once per second. Cumulative totals can therefore lag while a query or transaction is still running. Current activity is separate: pg_stat_activity (with track_activities) reports the command and state independently of those cumulative counters.

Until PostgreSQL 14 a separate "stats collector" process received the counts over a UDP socket and wrote files. Since PostgreSQL 15 the counters live in shared memory and there is no such process. They are saved to the pg_stat/ directory only on a clean shutdown; after a crash they start from zero.

Pick a stats_fetch_consistency value and watch step 3: it decides whether repeated reads inside one transaction see fresh or frozen numbers.

For DBAs and operators

Variable- and fixed-numbered stats

The shared area is a dynamic shared-memory hash table with one entry per object: databases, tables and indexes (pg_stat_database, pg_stat_all_tables, pg_statio_*), functions (only with track_functions = pl|all), replication slots and subscriptions (pg_stat_subscription_stats), and since PostgreSQL 18 per-backend I/O and WAL (pg_stat_get_backend_io(pid), pg_stat_get_backend_wal(pid)). Cluster-wide "fixed" counters sit next to it: pg_stat_archiver, pg_stat_bgwriter, pg_stat_checkpointer, pg_stat_io, pg_stat_slru, pg_stat_wal.

When counts become visible

Each process accumulates "pending" stats locally and flushes them just before going idle, but not more often than once per second (PGSTAT_MIN_INTERVAL). If an entry is locked by another flusher it retries later instead of waiting. So a transaction still in progress does not change the totals. Current activity (pg_stat_activity, needs track_activities) is different: it is updated immediately. In tests, SELECT pg_stat_force_next_flush(); makes the next flush happen at once.

Reading: stats_fetch_consistency

ValueInside one transaction
noneEvery access reads shared memory again: freshest, but two queries can disagree.
cache (default)An object's values are cached on first access until the transaction ends.
snapshotThe first access copies all stats of the current database: consistent, more memory and CPU.

SELECT pg_stat_clear_snapshot(); drops the cache/snapshot without ending the transaction. Monitoring tools that sit in one long transaction and see frozen numbers usually hit exactly this.

Persistence and resets

On a clean shutdown the checkpointer writes everything to pg_stat/pgstat.stat; the next start reads it back and removes the file. After an immediate shutdown, a crash, a restore from base backup or PITR, all counters are reset. The stats_temp_directory parameter was removed in 15. Resets: pg_stat_reset() (current database), pg_stat_reset_shared('bgwriter'|'checkpointer'|'io'|'wal'|'archiver'|'recovery_prefetch'|'slru'), pg_stat_reset_single_table_counters(oid), pg_stat_reset_backend_stats(pid) (18) and others; see the stats_reset columns.

-- which tables does autovacuum think are dirty?
SELECT relname, n_live_tup, n_dead_tup, n_mod_since_analyze,
       last_autovacuum, last_autoanalyze
  FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 10;

-- when were the counters last reset?
SELECT datname, stats_reset FROM pg_stat_database;

Support notes

  • After a crash n_dead_tup and n_mod_since_analyze start at 0, so autovacuum and autoanalyze may wait a long time before touching big tables. Run a manual VACUUM (ANALYZE) on busy tables after crash recovery or a failover to a promoted standby.
  • track_counts = off disables the table counters and with them autovacuum's decisions. Leave it on.
  • track_io_timing adds timings to pg_stat_io, pg_stat_database and EXPLAIN (BUFFERS); check the clock overhead with pg_test_timing first.
  • Rates need two samples: store snapshots of the views and compute deltas, the views are cumulative since stats_reset.
  • There is no process to see in ps or pg_stat_activity; every process (backends, autovacuum, checkpointer, io workers …) reports for itself.

For developers: inside the source

Where it lives

All in src/backend/utils/activity/: pgstat.c (core, flush, snapshot, file I/O; its header comment is the design document), pgstat_shmem.c (shared hash table and entry references), and one file per kind: pgstat_relation.c, pgstat_database.c, pgstat_io.c, pgstat_wal.c, pgstat_backend.c, pgstat_checkpointer.c … Internal types are in pgstat_internal.h, kind IDs in pgstat_kind.h. Note that pg_stat_activity is separate: backend_status.c.

Data structures

  • PgStat_Kind: built-in IDs from PGSTAT_KIND_DATABASE (1) to PGSTAT_KIND_WAL (12); each is described by a PgStat_KindInfo in pgstat_kind_builtin_infos[] with fixed_amount, write_to_file, shared_size and callbacks such as flush_pending_cb, flush_static_cb, reset_all_cb, snapshot_cb.
  • Variable-numbered stats sit in a dshash table on DSA, keyed by PgStat_HashKey (kind, dboid, objid); each PgStatShared_HashEntry points to a body that starts with PgStatShared_Common, whose LWLock protects the counters.
  • A backend keeps PgStat_EntryRefs (cached pointers to shared entries) whose pending field holds local, not-yet-flushed counts.
  • Fixed-numbered stats (archiver, bgwriter, checkpointer, io, slru, wal) live in PgStat_ShmemControl.

Call flow

  1. Executor and AM code call functions such as pgstat_count_heap_insert(), which only bump the backend-local PgStat_TableStatus in rel->pgstat_info (set up through pgstat_prep_pending_entry()); per-transaction counts are folded in at commit or abort. No shared memory is touched.
  2. Before going idle, PostgresMain() calls pgstat_report_stat(false). It returns early unless PGSTAT_MIN_INTERVAL (1000 ms) has passed, then runs pgstat_flush_pending_entries(nowait): each entry is locked with pgstat_lock_entry(…, nowait); if busy it stays pending. After PGSTAT_MAX_INTERVAL (60 s) it flushes with waiting. The returned timeout re-arms an idle timer (PGSTAT_IDLE_INTERVAL, 10 s).
  3. Readers: pgstat_fetch_entry() honours pgstat_fetch_consistency (PGSTAT_FETCH_CONSISTENCY_NONE/CACHE/SNAPSHOT); snapshot mode calls pgstat_build_snapshot().
  4. Shutdown: the checkpointer registers pgstat_before_server_shutdown(), which calls pgstat_write_statsfile() → pg_stat/pgstat.stat. At start-up the startup process runs pgstat_restore_stats(), or pgstat_discard_stats() if the previous run crashed (see StartupXLOG()).

Extension hooks

An extension loaded via shared_preload_libraries can add its own kind with pgstat_register_kind() (IDs from PGSTAT_KIND_CUSTOM_MIN; PGSTAT_KIND_EXPERIMENTAL = 24 for development). Working examples: injection_stats.c (variable-numbered) and injection_stats_fixed.c (fixed). The API is documented in xfunc.sgml.

Experimenting

  • Tests: stats.sql, stats.spec (visibility across sessions and consistency modes) and 029_stats_restart.pl (persistence across clean and crash restarts).
  • pg_stat_force_next_flush() and pg_stat_have_stats(kind, dboid, objid) are handy in tests.

Parameters that shape this

stats_fetch_consistency track_counts track_activities track_functions track_io_timing track_wal_io_timing track_activity_query_size track_cost_delay_timing

Related topics

Autovacuum: when tables get vacuumed Autovacuum launcher and workers Checkpointer Background writer Memory architecture Crash recovery ANALYZE and statistics Process model