Skip to content

Which PostgreSQL health numbers need an extension or a grant

A tour of the health surface organised by what is missing and why, because a panel that reads Not measured is the only version an operator can act on.

Open the monitoring tab on a stock PostgreSQL container and several panels answer with a label instead of a number. The slow-query list holds one row that reads pg_stat_statements extension not enabled. There is no buffer-pool figure at all. Checkpoint write time reads N/A. None of that is a fault in the connection - it is PostgreSQL telling you what it publishes to the role you connected as, on the version you are running, with the extensions you installed.

A dashboard has two ways to handle that. It can fill the gap with a plausible number, or it can name the gap. This one names the gap. What follows is which statistics view backs each panel, and what has to be true for that view to answer.

Which statistics view backs each panel

There is no metrics agent in the container and no sampling daemon. Every panel is a SELECT against PostgreSQL’s own statistics views, issued over the same pg connection pool the editor uses.

Panel View or function it reads
Health: connections, size, cache hit, sessions pg_stat_activity, pg_database_size, pg_statio_user_tables, pg_stat_statements
Overview: version, uptime, max connections, object counts version(), pg_postmaster_start_time(), pg_settings, pg_tables, pg_indexes
Performance: cache hit, deadlocks, checkpoint write time pg_statio_user_tables, pg_stat_database, pg_stat_bgwriter
Slow queries pg_stat_statements, falling back to pg_stat_activity
Sessions pg_stat_activity, excluding the app’s own backend
Tables: live and dead tuples, vacuum times, bloat pg_stat_user_tables plus the size functions
Indexes: type, size, scan count, usage ratio pg_stat_user_indexes, pg_index, pg_am
Storage pg_tablespace and the WAL functions

Reading the table as a list of dependencies is the useful move. Four of those rows carry a condition. pg_stat_statements is an optional extension, so the Health and Slow queries rows answer only where someone has installed it. The WAL functions behind Storage and the pg_stat_bgwriter counters behind Performance are privilege-gated, and pg_stat_bgwriter also changed shape in PostgreSQL 17. The remaining four rows read catalog and statistics views directly, with no optional extension in the path.

Slow queries without pg_stat_statements

This gap degrades in two different ways depending on which panel you are looking at, which is worth knowing before you read either one.

The dedicated slow-query view calls getSlowQueries(). Without the extension it falls back to a snapshot of pg_stat_activity: the queries running at the moment you looked. That is a different quantity from what the panel is for. pg_stat_statements accumulates per-statement totals since the last reset, so a short query that runs constantly rises to the top on volume alone. A live snapshot shows you whatever was in flight during one round trip, and a fast frequent query is rarely in it.

The health summary calls a lighter slow-query block, and that one does not fall back at all. It returns a single placeholder row reading pg_stat_statements extension not enabled. One panel shows the wrong shape of data with a label saying so; the other shows no data with a label saying why.

Both paths are wrapped in try/catch, so a missing extension never fails the monitoring request; it changes what the request can answer. Enabling pg_stat_statements is a server-side change made outside Studio, against the PostgreSQL documentation for your version - the provider installs nothing.

Until the view is there, treat the panel as a session list, not a workload profile.

Why there is no buffer-pool figure at all

The performance tab has a cache hit ratio and no buffer-pool usage. That asymmetry is deliberate and it was once a bug.

bufferPoolUsage used to be computed as blks_hit / (blks_hit + blks_read) from pg_stat_database. That expression is a cache hit ratio. The tab was therefore drawing the same quantity in two places with one of the two mislabelled, and when both counters were zero it substituted 100, so an untouched database reported a perfect buffer pool.

PostgreSQL publishes no buffer-pool occupancy figure. The number exists only through the pg_buffercache extension, which is not installed by default and whose scan takes a lock over shared_buffers. So the field is not reported. Not zero, not estimated - the panel does not exist, which is the only reading that does not mislead someone deciding whether to raise shared_buffers.

The cache hit ratio itself has the same discipline applied one level down. It comes from pg_statio_user_tables, and that view has nothing to divide in two ordinary situations: a database with no user tables, where the aggregate is NULL; and a table nothing has read yet, where heap_blks_hit and heap_blks_read are both 0 and the ratio is a division by zero. In both cases the panel reads “Not measured”. Both statements used to wrap the result in COALESCE(..., 100), so an unmeasured database was rated Excellent.

Checkpoint and WAL readings that need a grant

WAL size in the storage tab comes from the WAL functions, which are superuser-gated. A denial there is caught and the field is omitted rather than surfaced as an error, because a permission boundary is not an incident.

Checkpoint write time is the more interesting one, because it is gated by version as well as by privilege. PostgreSQL 17 moved checkpoint_write_time and checkpoint_sync_time out of pg_stat_bgwriter into pg_stat_checkpointer. On 17 and later the query against pg_stat_bgwriter throws, and checkpointWriteTime reports N/A. Measured through this provider on 2026-08-23 against postgres:18, that is what the panel shows. It is never 0.0s, which would be indistinguishable from a server that has genuinely written nothing since its counters were reset.

Deadlocks follow the same rule from the other direction. When pg_stat_database has no row for the connected database, deadlocks is absent from the response rather than reported as zero deadlocks. Absent means nobody counted. Zero means somebody counted and found none.

Absent, zero and Not measured are three different answers

The whole design of this surface is that those three states stay distinguishable. The boundary, stated plainly: without pg_stat_statements the slow-query list falls back to a live pg_stat_activity snapshot and health returns a placeholder row instead of a list. bufferPoolUsage is never reported on this engine. Per-second counters - transactionsPerSecond and queriesPerSecond - are absent entirely, because deriving them needs time-based sampling of pg_stat_database and the metric call is single-shot. And blocked on an active session is always false: lock-wait detection through pg_locks is not wired in, so an empty blocked column is a statement about the code, not about your locks.

That last one is the sharpest example of why the distinction matters. A false you believe is worse than a panel you know is missing, and it is the reason the monitoring surface reports absences as absences. PostgreSQL is the engine where nothing is held back in feature coverage, and it is still an engine whose health surface depends in places on an extension, a grant or a major version. Those are two compatible facts, and publishing both is cheaper than explaining one of them during an incident.