Metrics Guide¶
Complete reference for all 52 metrics tracked by pg_stat_insights.
Overview¶
pg_stat_insights tracks 52 comprehensive metrics across 8 categories:
| Category | Metrics | Description |
|---|---|---|
| Identity | 5 | Query identification and metadata |
| Planning | 6 | Query planning statistics |
| Execution | 7 | Query execution performance |
| Buffer I/O | 12 | Shared and local buffer operations |
| Temp I/O | 6 | Temporary file operations |
| WAL | 4 | Write-Ahead Log generation |
| JIT | 10 | Just-In-Time compilation stats |
| Parallel | 2 | Parallel query worker metrics |
Total: 52 metrics providing complete query performance visibility
Identity Metrics¶
userid¶
User OID who executed the query
- Type: oid
- Description: PostgreSQL user object ID
- Use: Filter queries by user, track user activity
-- Find queries by specific user
SELECT
r.rolname AS username,
COUNT(*) AS query_count,
SUM(calls) AS total_calls
FROM pg_stat_insights s
JOIN pg_roles r ON s.userid = r.oid
GROUP BY r.rolname
ORDER BY total_calls DESC;
dbid¶
Database OID where query executed
- Type: oid
- Description: PostgreSQL database object ID
- Use: Filter queries by database
-- Find queries by database
SELECT
d.datname AS database,
COUNT(*) AS query_count,
SUM(calls) AS total_calls,
ROUND(SUM(total_exec_time)::numeric, 2) AS total_time_ms
FROM pg_stat_insights s
JOIN pg_database d ON s.dbid = d.oid
GROUP BY d.datname
ORDER BY total_time_ms DESC;
toplevel¶
Is this a top-level query?
- Type: boolean
- Values:
true(client-issued),false(nested/internal) - Use: Filter by query origin
-- Compare top-level vs nested queries
SELECT
toplevel,
COUNT(*) AS query_count,
SUM(calls) AS total_calls,
ROUND(AVG(mean_exec_time)::numeric, 2) AS avg_time_ms
FROM pg_stat_insights
GROUP BY toplevel;
queryid¶
Unique query identifier (hash)
- Type: bigint
- Description: Normalized query hash for grouping
- Use: Join queries, track specific patterns
-- Track specific query over time
SELECT
queryid,
query,
calls,
total_exec_time,
stats_since
FROM pg_stat_insights
WHERE queryid = 1234567890;
query¶
Normalized query text
- Type: text
- Description: Query with constants replaced by parameters
- Use: Human-readable query identification
-- Search for queries by pattern
SELECT
query,
calls,
mean_exec_time
FROM pg_stat_insights
WHERE query LIKE '%my_table%'
ORDER BY mean_exec_time DESC
LIMIT 10;
Planning Metrics¶
Requires pg_stat_insights.track_planning = on
plans¶
Number of times query was planned
- Type: bigint
- Unit: count
- Use: Detect plan cache efficiency
-- Find queries that plan frequently
SELECT
query,
plans,
calls,
ROUND((plans::numeric / NULLIF(calls, 0)), 2) AS plan_per_call_ratio
FROM pg_stat_insights
WHERE plans > 0
ORDER BY (plans::numeric / NULLIF(calls, 0)) DESC
LIMIT 20;
total_plan_time¶
Total time spent planning (milliseconds)
- Type: float8
- Unit: milliseconds
- Use: Identify expensive planning
-- Top queries by planning time
SELECT
query,
plans,
ROUND(total_plan_time::numeric, 2) AS total_plan_ms,
ROUND(mean_plan_time::numeric, 2) AS avg_plan_ms
FROM pg_stat_insights
WHERE total_plan_time > 0
ORDER BY total_plan_time DESC
LIMIT 20;
min_plan_time, max_plan_time, mean_plan_time¶
Minimum, maximum, and average planning time
- Type: float8
- Unit: milliseconds
- Use: Analyze planning time distribution
-- Find queries with variable planning time
SELECT
query,
plans,
ROUND(min_plan_time::numeric, 3) AS min_ms,
ROUND(mean_plan_time::numeric, 3) AS avg_ms,
ROUND(max_plan_time::numeric, 3) AS max_ms,
ROUND((max_plan_time / NULLIF(min_plan_time, 1))::numeric, 1) AS variability
FROM pg_stat_insights
WHERE plans > 5
ORDER BY (max_plan_time / NULLIF(min_plan_time, 1)) DESC
LIMIT 20;
stddev_plan_time¶
Planning time standard deviation
- Type: float8
- Unit: milliseconds
- Use: Measure planning time consistency
-- Queries with inconsistent planning
SELECT
query,
plans,
ROUND(mean_plan_time::numeric, 2) AS avg_ms,
ROUND(stddev_plan_time::numeric, 2) AS stddev_ms,
ROUND((stddev_plan_time / NULLIF(mean_plan_time, 0) * 100)::numeric, 1) AS coefficient_of_variation
FROM pg_stat_insights
WHERE plans > 10
ORDER BY stddev_plan_time DESC
LIMIT 20;
Execution Metrics¶
Always tracked.
calls¶
Number of times query was executed
- Type: bigint
- Unit: count
- Use: Identify frequently-run queries
-- Most frequently executed queries
SELECT
query,
calls,
ROUND(mean_exec_time::numeric, 2) AS avg_ms,
ROUND((calls * mean_exec_time)::numeric, 2) AS total_time_ms
FROM pg_stat_insights
ORDER BY calls DESC
LIMIT 20;
total_exec_time¶
Total execution time (milliseconds)
- Type: float8
- Unit: milliseconds
- Use: Find time-consuming queries
-- Queries consuming most time
SELECT
query,
calls,
ROUND(total_exec_time::numeric, 2) AS total_ms,
ROUND((total_exec_time / SUM(total_exec_time) OVER () * 100)::numeric, 1) AS pct_of_total
FROM pg_stat_insights
ORDER BY total_exec_time DESC
LIMIT 20;
min_exec_time, max_exec_time, mean_exec_time¶
Minimum, maximum, and average execution time
- Type: float8
- Unit: milliseconds
- Use: Analyze execution time distribution
-- Find queries with high variability
SELECT
query,
calls,
ROUND(min_exec_time::numeric, 2) AS min_ms,
ROUND(mean_exec_time::numeric, 2) AS avg_ms,
ROUND(max_exec_time::numeric, 2) AS max_ms,
ROUND((max_exec_time - min_exec_time)::numeric, 2) AS range_ms,
CASE
WHEN min_exec_time > 0 THEN ROUND((max_exec_time / min_exec_time)::numeric, 1)
ELSE NULL
END AS variability_ratio
FROM pg_stat_insights
WHERE calls > 10
ORDER BY (max_exec_time - min_exec_time) DESC
LIMIT 20;
stddev_exec_time¶
Execution time standard deviation
- Type: float8
- Unit: milliseconds
- Use: Measure execution consistency
-- Queries with unstable performance
SELECT
query,
calls,
ROUND(mean_exec_time::numeric, 2) AS avg_ms,
ROUND(stddev_exec_time::numeric, 2) AS stddev_ms,
ROUND((stddev_exec_time / NULLIF(mean_exec_time, 0))::numeric, 3) AS relative_stddev
FROM pg_stat_insights
WHERE calls > 20 AND stddev_exec_time > 0
ORDER BY (stddev_exec_time / NULLIF(mean_exec_time, 0)) DESC
LIMIT 20;
rows¶
Total rows returned/affected
- Type: bigint
- Unit: rows
- Use: Analyze query result sizes
-- Queries returning most rows
SELECT
query,
calls,
rows,
ROUND((rows::numeric / NULLIF(calls, 0)), 0) AS avg_rows_per_call,
ROUND(mean_exec_time::numeric, 2) AS avg_ms,
ROUND((mean_exec_time / NULLIF(rows::numeric / NULLIF(calls, 0), 0))::numeric, 4) AS ms_per_row
FROM pg_stat_insights
WHERE rows > 0
ORDER BY rows DESC
LIMIT 20;
Buffer I/O Metrics¶
Shared Buffers¶
Shared buffer operations (PostgreSQL buffer cache)
| Metric | Description | Unit |
|---|---|---|
shared_blks_hit | Blocks found in cache | blocks (8KB each) |
shared_blks_read | Blocks read from disk | blocks (8KB each) |
shared_blks_dirtied | Blocks modified | blocks |
shared_blks_written | Blocks written to disk | blocks |
Cache Hit Ratio:
SELECT
query,
shared_blks_hit,
shared_blks_read,
ROUND((shared_blks_hit::numeric /
NULLIF(shared_blks_hit + shared_blks_read, 0))::numeric, 3) AS cache_hit_ratio,
pg_size_pretty((shared_blks_read * 8192)::bigint) AS data_from_disk
FROM pg_stat_insights
WHERE (shared_blks_hit + shared_blks_read) > 0
ORDER BY shared_blks_read DESC
LIMIT 20;
Local Buffers¶
Local buffer operations (temporary tables)
| Metric | Description |
|---|---|
local_blks_hit | Local blocks in cache |
local_blks_read | Local blocks from disk |
local_blks_dirtied | Local blocks modified |
local_blks_written | Local blocks written |
Temp Buffers¶
Temporary file operations (work_mem overflow)
| Metric | Description |
|---|---|
temp_blks_read | Temp blocks read |
temp_blks_written | Temp blocks written |
-- Find queries using temp files
SELECT
query,
calls,
temp_blks_read,
temp_blks_written,
pg_size_pretty((temp_blks_written * 8192)::bigint) AS temp_written_size
FROM pg_stat_insights
WHERE temp_blks_written > 0
ORDER BY temp_blks_written DESC
LIMIT 20;
I/O Timing Metrics¶
Requires pg_stat_insights.track_io_timing = on
Block Timing¶
Time spent on I/O operations (milliseconds)
| Metric | Description |
|---|---|
shared_blk_read_time | Time reading shared blocks |
shared_blk_write_time | Time writing shared blocks |
local_blk_read_time | Time reading local blocks |
local_blk_write_time | Time writing local blocks |
temp_blk_read_time | Time reading temp blocks |
temp_blk_write_time | Time writing temp blocks |
-- Find slow I/O operations
SELECT
query,
calls,
shared_blks_read,
ROUND(shared_blk_read_time::numeric, 2) AS read_time_ms,
ROUND((shared_blk_read_time / NULLIF(shared_blks_read, 0))::numeric, 3) AS ms_per_block,
CASE
WHEN shared_blk_read_time / NULLIF(shared_blks_read, 0) > 10 THEN '[SLOW] Very Slow'
WHEN shared_blk_read_time / NULLIF(shared_blks_read, 0) > 1 THEN '[WARNING] Slow'
ELSE '[OK] Normal'
END AS disk_speed
FROM pg_stat_insights
WHERE shared_blks_read > 0
ORDER BY (shared_blk_read_time / NULLIF(shared_blks_read, 0)) DESC
LIMIT 20;
WAL Metrics¶
Requires pg_stat_insights.track_wal = on
wal_records¶
Number of WAL records generated
- Type: bigint
- Unit: count
- Use: Track write activity
wal_fpi¶
Full Page Images written
- Type: bigint
- Unit: count
- Use: Monitor checkpoint impact
wal_bytes¶
Total WAL bytes generated
- Type: numeric
- Unit: bytes
- Use: Capacity planning, replication monitoring
wal_buffers_full¶
Times WAL buffers were full
- Type: bigint
- Unit: count
- Use: Detect WAL buffer saturation
-- Comprehensive WAL analysis
SELECT
query,
calls,
wal_records,
wal_fpi,
pg_size_pretty(wal_bytes::bigint) AS wal_size,
wal_buffers_full,
ROUND((wal_records::numeric / NULLIF(calls, 0)), 2) AS records_per_call,
pg_size_pretty((wal_bytes / NULLIF(calls, 0))::bigint) AS bytes_per_call
FROM pg_stat_insights
WHERE wal_bytes > 0
ORDER BY wal_bytes DESC
LIMIT 20;
JIT Metrics¶
Requires pg_stat_insights.track_jit = on
JIT Compilation¶
Just-In-Time compilation statistics
| Metric | Description | Unit |
|---|---|---|
jit_functions | Functions JIT compiled | count |
jit_generation_time | Code generation time | ms |
jit_inlining_count | Functions inlined | count |
jit_inlining_time | Inlining time | ms |
jit_optimization_count | Optimizations performed | count |
jit_optimization_time | Optimization time | ms |
jit_emission_count | Code emissions | count |
jit_emission_time | Emission time | ms |
jit_deform_count | Tuple deforming ops | count |
jit_deform_time | Deforming time | ms |
-- Analyze JIT cost vs benefit
SELECT
query,
calls,
jit_functions,
ROUND(jit_generation_time::numeric, 2) AS jit_gen_ms,
ROUND(mean_exec_time::numeric, 2) AS exec_ms,
ROUND((jit_generation_time / NULLIF(mean_exec_time, 0) * 100)::numeric, 1) AS jit_overhead_pct,
CASE
WHEN (jit_generation_time / NULLIF(mean_exec_time, 0) * 100) > 10 THEN '[WARNING] High Overhead'
WHEN jit_functions > 0 THEN '[OK] Using JIT'
ELSE '[NONE] No JIT'
END AS jit_status
FROM pg_stat_insights
WHERE mean_exec_time > 0
ORDER BY jit_generation_time DESC
LIMIT 20;
Parallel Query Metrics¶
Requires pg_stat_insights.track_parallel_queries = on
parallel_workers_to_launch¶
Number of parallel workers planned
- Type: bigint
- Unit: workers
- Use: Evaluate parallelization planning
parallel_workers_launched¶
Number of parallel workers actually launched
- Type: bigint
- Unit: workers
- Use: Monitor parallel execution
-- Parallel query efficiency
SELECT
query,
calls,
parallel_workers_to_launch AS planned,
parallel_workers_launched AS actual,
ROUND((parallel_workers_launched::numeric /
NULLIF(parallel_workers_to_launch, 0) * 100), 1) AS efficiency_pct,
ROUND(mean_exec_time::numeric, 2) AS avg_ms,
rows
FROM pg_stat_insights
WHERE parallel_workers_to_launch > 0
ORDER BY calls DESC
LIMIT 20;
Timestamp Metrics¶
stats_since¶
When statistics collection started for this query
- Type: timestamp with time zone
- Use: Data freshness tracking
minmax_stats_since¶
When min/max tracking started
- Type: timestamp with time zone
- Use: Min/max data validity period
-- Check stats freshness
SELECT
query,
calls,
stats_since,
NOW() - stats_since AS stats_age,
minmax_stats_since,
NOW() - minmax_stats_since AS minmax_age
FROM pg_stat_insights
WHERE stats_since < NOW() - INTERVAL '1 hour'
ORDER BY stats_since
LIMIT 10;
Metric Relationships¶
Cache Hit Ratio¶
Calculated from buffer metrics
| Ratio | Performance | Action |
|---|---|---|
| > 0.99 | Excellent [OK] | No action needed |
| 0.95-0.99 | Good [OK] | Monitor |
| 0.90-0.95 | Fair [WARNING] | Consider optimization |
| < 0.90 | Poor [CRITICAL] | Optimize immediately |
Execution Efficiency¶
Time per row returned
I/O Efficiency¶
Time per block read
Typical values: - SSD: 0.1-1 ms/block - HDD: 1-10 ms/block - Network storage: 5-50 ms/block
Metric Aggregations¶
Sum Metrics¶
Metrics that can be summed across queries:
calls,rowstotal_plan_time,total_exec_time- All
*_blks_*metrics - All
wal_*metrics - All
jit_*_countmetrics
-- Total across all queries
SELECT
COUNT(*) AS total_queries,
SUM(calls) AS total_calls,
SUM(rows) AS total_rows,
ROUND(SUM(total_exec_time)::numeric, 2) AS total_time_ms,
SUM(shared_blks_read) AS total_blocks_read,
pg_size_pretty(SUM(wal_bytes)::bigint) AS total_wal
FROM pg_stat_insights;
Average Metrics¶
Metrics that should be averaged:
mean_plan_time,mean_exec_timecache_hit_ratio- JIT timing metrics
-- Average across all queries
SELECT
ROUND(AVG(mean_exec_time)::numeric, 2) AS avg_exec_time,
ROUND(AVG(cache_hit_ratio)::numeric, 3) AS avg_cache_ratio,
ROUND(AVG(parallel_workers_launched)::numeric, 2) AS avg_workers
FROM pg_stat_insights
WHERE calls > 0;
Metric Best Practices¶
[OK] Recommended Metrics to Monitor¶
Always Monitor: - total_exec_time - Find expensive queries - calls - Identify hot paths - mean_exec_time - Detect slow queries - cache_hit_ratio - Optimize caching - rows - Understand query impact
Monitor in Production: - wal_bytes - Track writes - shared_blks_read - Monitor I/O - stddev_exec_time - Detect variability
Monitor in Development: - plans - Plan caching - JIT metrics - JIT effectiveness - Parallel metrics - Parallelization efficiency
Metric Thresholds¶
Recommended Alert Thresholds¶
-- Define monitoring thresholds
CREATE VIEW performance_alerts AS
SELECT
queryid,
LEFT(query, 100) AS query_preview,
CASE
WHEN mean_exec_time > 10000 THEN 'CRITICAL: >10s avg'
WHEN mean_exec_time > 1000 THEN 'WARNING: >1s avg'
WHEN mean_exec_time > 100 THEN 'INFO: >100ms avg'
ELSE 'OK'
END AS execution_alert,
CASE
WHEN cache_hit_ratio < 0.80 THEN 'CRITICAL: <80% cache'
WHEN cache_hit_ratio < 0.90 THEN 'WARNING: <90% cache'
WHEN cache_hit_ratio < 0.95 THEN 'INFO: <95% cache'
ELSE 'OK'
END AS cache_alert,
CASE
WHEN wal_bytes > 100000000 THEN 'WARNING: >100MB WAL'
WHEN wal_bytes > 10000000 THEN 'INFO: >10MB WAL'
ELSE 'OK'
END AS wal_alert
FROM pg_stat_insights
WHERE calls > 10;
-- Check for alerts
SELECT * FROM performance_alerts
WHERE execution_alert != 'OK'
OR cache_alert != 'OK'
OR wal_alert != 'OK';
Next Steps¶
- Views Reference - Explore all 11 views
- Configuration - Configure tracking
- Usage Examples - Real-world queries
- Troubleshooting - Common issues