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 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 is running right now?
Section titled “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, queryFROM pg_stat_activityWHERE 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
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?
Section titled “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, queryFROM pg_stat_statementsORDER BY total_exec_time DESCLIMIT 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
Section titled “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?
Section titled “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?
Section titled “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.
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_activitywherestate = 'active', ordered bynow() - query_start, shows live work;pg_stat_statementsbytotal_exec_timeshows cumulative cost.Is high CPU I/O or compute?
Checkwait_event_type: no wait event means running on CPU;IOorLockmeans 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.