Skip to main content

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 where state = 'active' and backend_type = 'client backend'), rank cumulative cost in pg_stat_statements by total_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 active rows running the same query shape → a hot query hammering the CPU (often an un-indexed one, run constantly).
  • A few very long-running active queries → an expensive scan, sort, or aggregate.
  • wait_event_type tells you compute vs waiting: no wait event = running on CPU; IO or Lock = 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 seeLikely causeFix
Top query does a Seq Scan on a big tableMissing indexAdd the index (find missing indexes)
A query that used to be fast is now hotPlan regression from stats driftANALYZE; see why it got slow
Hundreds of active backendsToo many connectionsAdd a connection pooler
Slow sorts/hashes spilling to diskUnder-sized work_mem or SELECT * moving too muchSelect 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_activity for a pile-up of the same active query.
  • Keep pg_stat_statements sorted by total_exec_time in 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.