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 is 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 is 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 are not the application queries you are 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.