CPU Bottlenecks
Telling a genuine CPU limit from queueing, and the query patterns that burn CPU unnecessarily.
High CPU is not a problem by itself. A database at 70% CPU with latency inside its budget is a well-used machine. The problem is CPU saturation causing queueing.
Is it actually CPU?
Check three things before concluding the database needs more cores:
- Run queue length.
load averageabove the core count means processes are waiting for CPU. - The wait breakdown. If sessions are waiting on I/O or locks rather than running, the CPU is not the limit.
- Whether the work is necessary. Most "CPU bound" databases are executing queries that should not exist.
-- PostgreSQL: what are active sessions actually doing?
SELECT wait_event_type, wait_event, count(*)
FROM pg_stat_activity WHERE state = 'active'
GROUP BY 1, 2 ORDER BY 3 DESC;A wait_event_type of NULL means genuinely running on CPU. IO means storage. Lock means
contention — see Lock Contention.
Where database CPU goes
- Query execution — the work you intend.
- Sorting and hashing — often avoidable with the right index.
- Parsing and planning — significant when the workload is many short, unprepared statements.
- Compression and checksums — real, especially in column stores and on TLS connections.
- Background maintenance — vacuum, compaction, merges. Necessary work that competes with queries.
- Replication apply on replicas, frequently single-threaded.
The usual causes
A missing index. Scanning a million rows to return ten burns CPU on the comparison, and reading them burns I/O. This is the first thing to check.
Sorting that an index could provide. An ORDER BY that matches an index's order costs nothing;
one that does not sorts the result set every time.
Row-by-row processing. A thousand single-row round trips cost far more in parsing, planning and network handling than one batched statement.
Unprepared statements. Re-parsing and re-planning identical queries with different literals. Prepared statements or a plan cache remove that cost entirely.
Over-parallelism. More parallel workers per query than the machine can run concurrently means context switching rather than throughput.
Compression settings tuned for space in a workload that is CPU-limited. This is a real trade, not a fault, and worth revisiting when CPU is the constraint.
Reducing it
- Fix the top queries by total time, not the slowest one. See Finding Slow Queries.
- Add the index that removes a scan, or the composite index that removes a sort.
- Batch round trips.
- Use prepared statements.
- Move analytical work off the transactional database — a replica, or an analytical store fed by change data capture.
- Cache what is recomputed constantly, being deliberate about the failure modes a cache introduces.
Only after those: more cores, or faster ones. Note that single-thread speed matters more than core count for engines like Redis, and for single-threaded phases like replication apply.