# 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:

```sql
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:

```sql
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](https://www.dbgorilla.com/learn/postgres/how-to-identify-slow-queries-in-postgresql/)
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](https://www.dbgorilla.com/learn/postgres/how-to-find-missing-indexes-in-postgresql/)) |
| A query that used to be fast is now hot | Plan regression from stats drift | `ANALYZE`; see [why it got slow](https://www.dbgorilla.com/learn/postgres/why-did-my-postgres-query-suddenly-get-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_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 →](https://app.dbgorilla.com/signup)

## 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.

## Related

- [How to identify slow queries in PostgreSQL](https://www.dbgorilla.com/learn/postgres/how-to-identify-slow-queries-in-postgresql/)
- [How to find missing indexes in PostgreSQL](https://www.dbgorilla.com/learn/postgres/how-to-find-missing-indexes-in-postgresql/)
- [PostgreSQL connection pooling and "too many connections"](https://www.dbgorilla.com/learn/postgres/postgresql-connection-pooling-too-many-connections/)
- Product: [DBGorilla docs](https://www.dbgorilla.com/docs/)