How to fix high CPU on a PostgreSQL database
Quick answer: High CPU is most often the queries, not the config — start there. See what's running now in
pg_stat_activity(rows wherestate = 'active'andbackend_type = 'client backend'), rank cumulative cost inpg_stat_statementsbytotal_exec_time, and fix the cause — usually a missing index forcing sequential scans, a plan that regressed after stats drift, or too many active connections. Reaching for a config knob first is the common mistake.
Step 1: what's running right now?
pg_stat_activity is the live view — one row per connection:
SELECT pid, state, wait_event_type, wait_event,
now() - query_start AS running_for,
query
FROM pg_stat_activity
WHERE state = 'active'
AND backend_type = 'client backend'
ORDER BY running_for DESC;
Filter on backend_type = 'client backend' or the list fills with autovacuum
workers, parallel workers, and replication processes — which are also active,
but aren't the application queries you're hunting.
- Many
activerows running the same query shape → a hot query hammering the CPU (often an un-indexed one, run constantly). - A few very long-running
activequeries → an expensive scan, sort, or aggregate. wait_event_typetells you compute vs waiting: no wait event = running on CPU;IOorLock= blocked, not compute-bound.
Step 2: what costs the most over time?
Live activity shows the moment; pg_stat_statements shows the pattern:
SELECT calls, total_exec_time, mean_exec_time, query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;
The top of this list is where your CPU goes. See
identifying slow queries
for how to read it — remember total_exec_time = calls × mean_exec_time, so a
cheap query run constantly can dominate.
Step 3: match the symptom to the cause
| What you see | Likely cause | Fix |
|---|---|---|
Top query does a Seq Scan on a big table | Missing index | Add the index (find missing indexes) |
| A query that used to be fast is now hot | Plan regression from stats drift | ANALYZE; see why it got slow |
| Hundreds of active backends | Too many connections | Add a connection pooler |
| Slow sorts/hashes spilling to disk | Under-sized work_mem or SELECT * moving too much | Select fewer columns; tune work_mem carefully — note a spill is mostly I/O, with some added CPU from extra merge passes |
Notice how few of these are "change a setting." The lever is usually the query or an index — though not always. Configuration and contention can drive CPU on their own: far more connections than cores (context-switch thrash), no connection pooler in front of a serverless app, or JIT compilation firing on short queries where it costs more than it saves. Rule the queries out first, then look there.
Why does AI-generated code cause CPU spikes?
Your AI coding agent (Claude Code, Cursor) writes queries that are correct but
blind to cost: an un-indexed filter that becomes a Seq Scan, an N+1 loop that
fires thousands of small queries, a SELECT * that hashes far more data than
needed. None of that looks expensive in the source. CPU load is an emergent
property of those queries meeting production data volume — which the model never
sees.
How do I keep CPU under control?
- Watch
pg_stat_activityfor a pile-up of the same active query. - Keep
pg_stat_statementssorted bytotal_exec_timein a dashboard. - Index the scans, fix the regressions, and pool the connections before touching server config.
How DBGorilla helps
DBGorilla connects read-only and gives your AI coding agent (Claude Code, Cursor)
the live picture — the active queries in pg_stat_activity, the top
total_exec_time statements, the plans behind them — so it can point at the
query burning CPU and explain the fix (an index, a rewrite, pooling) instead of
guessing at config. It surfaces and explains; it does not touch production.
Get started free →
FAQ
What causes high CPU usage in PostgreSQL? Most often the queries: missing indexes forcing sequential scans, a plan regression, expensive sorts/aggregates, or too many active connections. Config and contention (connection storms, JIT on short queries) can also be the primary cause — start with the queries, then look there.
How do I find which query is burning CPU right now?
pg_stat_activity where state = 'active', ordered by now() - query_start,
shows live work; pg_stat_statements by total_exec_time shows cumulative cost.
Is high CPU I/O or compute?
Check wait_event_type: no wait event means running on CPU; IO or Lock means
blocked. High CPU with little waiting is raw query work.
Will raising max_connections or shared_buffers fix it? Usually not — the queries are the cause. Index, fix the plan, or pool; raising limits often just runs more expensive work at once.