Queries feel slower than expected and I/O is elevated — checking the buffer cache hit ratio in pg_stat_database shows a high proportion of disk reads.
Diagnose it
-- Cache hit ratio by database (run as superuser):
SELECT datname,
blks_hit,
blks_read,
round(
100.0 * blks_hit / NULLIF(blks_hit + blks_read, 0),
2) AS cache_hit_pct,
pg_size_pretty(pg_database_size(datname)) AS db_size
FROM pg_stat_database
WHERE datname NOT IN ('template0', 'template1')
ORDER BY cache_hit_pct ASC NULLS LAST;
Interpretation (guidance): for OLTP workloads a hit ratio above
95–99% is typical; OLAP or large sequential scans will naturally be lower and should not be
used to justify shared_buffers increases — sequential scans use ring buffers that bypass
shared_buffers by design.
Why it happens
blks_hit counts blocks found in the PostgreSQL buffer pool (shared_buffers);
blks_read counts blocks fetched from the OS page cache or disk. A falling
hit ratio can indicate: a growing working set that no longer fits in shared_buffers,
new query patterns with large sequential scans, or recent eviction pressure from another workload.