Skip to content
dbexplore

Glossary: Monitoring

Cache hit ratio, and why it misleads

Also called: buffer hit ratio, blks_hit.

Definition, revised in place. Last updated .

The cache hit ratio is the share of block requests PostgreSQL answered from its own shared buffer pool rather than asking the operating system for the block. It is computed from two cumulative counters, blks_hit and blks_read, in pg_stat_database. The name invites a reading it does not support: a miss is not a disk read, it is a request that left the buffer pool. Where the block then came from, memory or storage, these two counters cannot say.

Three reasons the number lies

The first is the one above. A block counted in blks_read may have been served by the operating system page cache in microseconds, so a server showing a lower ratio can be entirely memory-resident, and a server showing a high one can be waiting on storage for the small remainder. The counters describe a boundary inside the process, not a boundary between memory and disk.

The second is that both counters are cumulative since the statistics were last reset. A ratio taken over the lifetime of a cluster averages every workload it has ever run, including the bulk load somebody did at midnight, and it moves so slowly that an incident lasting an hour is invisible in it. The only honest form is a ratio of deltas over a window you chose.

The third is that a low ratio is not necessarily bad and a high one is not necessarily good. A nightly report that scans a large table will drive the ratio down while behaving exactly as intended, and a cluster that has quietly lost its working set to a ring buffer can hold a high ratio while every query that matters goes to storage. The number has no target value, which is unfortunate, because a target value is the only thing most dashboards do with it.

Reading the two counters honestly

The ratio is one line, and the point of running it here is what the parts look like beside it.

SELECT blks_hit,
       blks_read,
       round(100.0 * blks_hit / nullif(blks_hit + blks_read, 0), 2) AS naive_hit_ratio
FROM pg_stat_database
WHERE datname = current_database();
 blks_hit | blks_read | naive_hit_ratio 
----------+-----------+-----------------
     1300 |        66 |           95.17
(1 row)

That is a database seconds old on 14.24, and the ratio it reports is a statement about the few hundred blocks it has touched since it was created rather than about anything worth alerting on. Take the same reading twice a minute apart, subtract, and the result at least describes the minute.

From PostgreSQL 16 there is a better instrument for the underlying question. pg_stat_io splits the same activity by process type, object and context, and separates reads from extends and writebacks, so “who is doing the physical I/O” stops being an inference. The page on the I/O statistics view in PostgreSQL 16 covers what it reports and what it costs to turn the timing on.

What to measure instead

If the question behind the ratio is whether the server has enough memory, the answer lives in the relationship between the buffer pool and the operating system cache underneath it, which double buffering describes and which no single ratio can express. If the question is what changed in the newer majors and which panels that breaks, what PostgreSQL 18 changed in monitoring has the accounting.

Put every Postgres you run on autopilot.

We onboard teams in small batches. Tell us about your fleet and we will reach out when a seat opens. One email, no drip campaign.