A practical, step-by-step 3-tier framework for diagnosing PostgreSQL performance bottlenecksβfrom live lock triage to OS log audits and pg_profile historical analysis.
- π Stop the Guesswork: Learn a structured, step-by-step decision tree to diagnose PostgreSQL bottlenecks under pressure without running random queries.
- β‘ Master 3-Tier Triage: Move seamlessly from Live In-DB Lock Analysis $\rightarrow$ OS & Log Diagnostics $\rightarrow$ Historical
pg_profileDeep Dives. - π Uncover Hidden Bottlenecks: Trace blocking session trees, detect
work_memmemory spills to disk, and spot optimizer blind spots in real time. - π Free Master Cheat Sheet: Download a ready-to-use Excel workbook packed with copy-paste SQL scripts,
grepcommands, and snapshot queries!
π From Slow Query to Root Cause Using PostgreSQL Monitoring Tools
π Extensions Used
- π pg_stat_statements β Query execution statistics
- β³ pg_wait_sampling β Wait event sampling
- π dblink β Required by
pg_profile - π pg_profile β Historical performance analysis
Disclaimer: This guide and the downloadable cheat sheet are provided for educational purposes only. Always validate commands in a test environment before running them on live systems. Use at your own risk.
1οΈβ£ Section 1: The Master Diagnostic Flowchart
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PERFORMANCE ISSUE DETECTED IN POSTGRES β
ββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββ
β TIER 1: Live In-Database Triage β
β βββΊ Check Lock Tree (Find blocking PIDs) β
β βββΊ Identify Long-Running Queries (>5m) β
β βββΊ Check Memory Cache Hit Ratio (<99%) β
ββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββ
β TIER 2: Operating System & Log Diagnostics β
β βββΊ Filter Critical Errors (ERROR, FATAL, PANIC) β
β βββΊ Scan for "temporary file:" (work_mem spill) β
β βββΊ Check WAL Checkpoint Frequency β
ββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββ
β TIER 3: Historical Deep-Dive (pg_profile) β
β βββΊ Compare Snapshot Windows (Baseline vs Spike) β
β βββΊ Top Time-Consuming Queries β
β βββΊ Table & Index Write Churn Analysis β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
2οΈβ£ Section 2: Walkthrough β Tier 1: Live In-Database Triage
(Refers to 01-performance-checklist in your Excel sheet)
When the database is actively choking or application connections are queuing up, do not panic and kill random queries. Follow this exact sequence:
- Step 1: Unblock the Connection Cascade (
check for blocking session)- Why: A single process holding an exclusive lock on a core table will cause every subsequent query touching that table to wait.
- Action: Run the recursive lock tree query to find the Leader PID (depth 1).
- Remediation: Attempt a graceful cancellation first (
SELECT pg_cancel_backend(pid)). If the process isidle in transaction, forcefully terminate it (SELECT pg_terminate_backend(pid)).
- Step 2: Inspect Active Lock Modes (
check for locking session)- Why: Identify whether sessions are competing for row-level locks or full relation locks (
AccessExclusiveLock).
- Why: Identify whether sessions are competing for row-level locks or full relation locks (
- Step 3: Scan for Runaway SQL (
identify the long running queries)- Why: Catch un-indexed, heavy transactions that have been running longer than 5 minutes before they exhaust memory or CPU.
- Step 4: Check Engine RAM Efficiency (
The Cache Hit Ratio Query)- Why: If your shared buffer hit ratio drops below 99%, your database is forcing heavy disk I/O reads instead of serving data from RAM.
- Step 5: Identify Optimizer Blindspots (
Stale Statistics)- Why: Tables with a high volume of un-analyzed changes (
n_mod_since_analyze) fool the PostgreSQL query planner into choosing bad execution paths (e.g., choosing a sequential scan over an index scan).
- Why: Tables with a high volume of un-analyzed changes (
3οΈβ£ Section 3: Walkthrough β Tier 2: OS & Log File Audits
(Refers to 02-logfile-checks in your Excel sheet)
If live triage doesn’t immediately reveal an active lock contention, look at the engine logs on the server:
- Locate Configuration & Log Paths: Confirm active setting files (
pg_file_settings) and active log paths (pg_current_logfile()). - Filter Out User Syntax Noise: Use
awkto filter for real database panics, fatal errors, and storage failures (ERROR:,FATAL:,PANIC:). - Scan for Memory Overshot (
temporary file:): Search for temporary file creations. When PostgreSQL logstemporary file:, it means queries exceededwork_memand spilled their sort or hash aggregate operations directly to disk storage. - Audit Checkpoint Frequency: Search logs for
checkpoint occurring too frequently. This indicatesmax_wal_sizeis too small, forcing premature WAL flushes that degrade write throughput.
4οΈβ£ Section 4: Walkthrough β Tier 3: Historical Deep-Dive (pg_profile)
(Refers to 03-pg_profile-checklist in your Excel sheet)
For post-mortem analysis (Root Cause Analysis / RCA) or capacity planning.
- Capture & List Snapshot Windows: Trigger samples (
SELECT profile.take_sample();) and map sample IDs to the specific incident timeframe. - Audit Macro Throughput: Check transaction commits vs. rollbacks and deadlock counts across snapshot intervals.
- Isolate Top Time-Consuming SQL: Rank query IDs by total runtime to pinpoint queries needing index tuning or structural rewriting.
- Track Table Write Churn & Index Utilization: Identify tables accumulating dead tuples and verify whether maintenance jobs (vacuum/analyze) were performed during the incident window.
β οΈ Important Note on Schema Names:
Throughout the cheat sheet and queries in this section, we assume pg_profile is installed in its default schema name: profile (e.g., profile.take_sample() or FROM profile.samples).
In customer or production environments, DBAs may install the extension in a custom schema (such as perf, monitoring, or dba). Be sure to replace the word profile in these queries with your environment’s specific schema name!