This guide shows developers and operators how to use Amazon RDS Performance Insights to find out why a database is slow. Performance Insights is the AWS dashboard that records how many database sessions are busy at each moment and what they are waiting on. The usual complaint is vague. The app feels slow, CPU looks fine, and nobody knows whether to add an index, buy a bigger instance or look at the application.
The answer is one chart, read the right way. It plots database load in average active sessions (AAS), the number of sessions running or waiting at the same time, against the instance’s vCPU count (its virtual CPUs). When load climbs above that line, the database is saturated. A wait event is the label that says what each session is waiting for. Slicing the load by wait event shows whether the pressure comes from CPU work, disk reads, locks or commit flushes (the disk write each confirmed transaction waits for).
What’s happening
Every second, the engine samples which sessions are active and what state they are in. An active session is either running on CPU or waiting on something — an I/O read, a lock held by another transaction, a WAL/redo flush, the client itself. Performance Insights aggregates those samples into DB load (AAS) and lets you slice it by wait event, SQL statement, host, user, or database.
The mental model is capacity against demand. An instance with 8 vCPUs can execute 8 sessions’ worth of CPU work simultaneously; AAS sitting at 2 means comfortable headroom, AAS at 15 means seven sessions are queued or waiting at any instant. But the composition matters more than the number:
- CPU dominant — queries are doing the work; the fix is query plans, indexes, or more compute.
- IO waits dominant (
IO:DataFileReadon PostgreSQL,io/file/innodb/innodb_data_fileon MySQL) — the working set does not fit in memory or queries scan too much; the fix is indexes, query shape, or more RAM, not more CPU. - Lock waits dominant (
Lock:transactionid,Lock:tupleon PostgreSQL;synch/mutex families on MySQL) — sessions are blocking each other; the fix is in the application’s transaction patterns, and no instance size will help. - Commit waits dominant (
LWLock:WALWrite,IO:XactSyncon Aurora PostgreSQL; binlog sync events on MySQL) — write-heavy workload flushing on every commit; the fix is batching, or accepting the durability cost. Client:ClientRead/ network waits — the database is waiting on the client to consume results; the bottleneck is the app tier or a slow network path, and tuning the database is wasted effort.
Retention on the free tier is 7 days; longer retention is a paid setting, and AWS has been folding this tooling into CloudWatch Database Insights, where the same load-by-wait analysis carries over. Feature and engine support details are in the RDS docs at https://docs.aws.amazon.com/rds/.
What to check first
- Is Performance Insights even enabled — it is a per-instance setting, and it cannot show you an incident it was not on for.
- The vCPU line on the load chart — everything is read relative to it, so know the instance class.
- The time window — zoom to the incident window; an all-day average flattens the spike you care about.
- Whether the pattern is new — compare the same hours last week before declaring an emergency.
- Replica vs writer — check which instance you are looking at; read replicas have their own dashboards and their own problems, especially in read-heavy replica designs.
How to verify
Console: RDS > Performance Insights, pick the instance, set Slice by to Waits. The stacked colours under the load line are your diagnosis; the Top SQL tab below attributes the same load to statements.
The same data is scriptable — note the API uses the DbiResourceId, not the instance name:
aws pi describe-dimension-keys \
--service-type RDS \
--identifier db-ABCDEFGHIJKL123 \
--metric db.load.avg \
--group-by '{"Group":"db.wait_event"}' \
--start-time 2026-07-03T13:00:00Z \
--end-time 2026-07-03T15:00:00Z \
--period-in-seconds 300
Swap --group-by to {"Group":"db.sql_tokenized"} to rank statements by the load they generate. Cross-check with CloudWatch basics (CPUUtilization, ReadIOPS, FreeableMemory) — Performance Insights tells you what sessions wait on; CloudWatch tells you what the host is doing.
The runbook
- Establish saturation or not. AAS below the vCPU line with slow app response usually means the problem is not database capacity — look at
Client:ClientRead, connection acquisition in the app, or the network before touching the instance. - Identify the dominant wait during the bad window. One wait event usually owns half or more of the load. That event picks the playbook — do not skip to a favourite fix.
- Attribute it to SQL. Slice by Top SQL under the same window. Nine times out of ten, one or two tokenized statements carry the load; capture them and run
EXPLAIN/EXPLAIN ANALYZEon realistic parameters. - Apply the wait-specific fix. IO reads: add the missing index or widen memory. CPU: fix the plan or scale compute. Locks: shorten transactions and fix retry storms in the app. Commit flushes: batch writes, review
synchronous_commit/binlog settings against your durability requirements — parameter changes belong in a reviewed parameter group diff, not a console hot-edit. - Verify on the same chart. After the change, the same window shape should show the dominant wait shrunk and total AAS lower — not just “the app feels faster”.
- Record the baseline. Screenshot or export the healthy load profile; the next incident is diagnosed by diff, which is much faster.
When this is the wrong move
Performance Insights samples sessions — it will not catch a 30-millisecond query that runs a million times unless it shows up in aggregate load, and it says nothing about problems outside the database: connection pool exhaustion in the app, DNS, or a saturated NAT path. Sub-second micro-latency analysis belongs to engine tools (pg_stat_statements timing percentiles, slow query log). And if the instance is simply undersized for a workload that doubled, no amount of wait-event reading substitutes for the capacity conversation.
Common mistakes
- Reading AAS as CPU percent — 60% CPU with AAS at three times vCPUs is a crisis; 95% CPU with AAS below vCPUs may be a healthy, well-used box.
- Tuning the database for
Client:ClientReadwaits that belong to the application tier. - Averaging over a day and concluding “load is fine” when the 14:00 spike is the entire problem.
- Leaving Performance Insights off to save the paid retention, then having 7 days of history that do not include the incident everyone cares about.
Keeping it under control
- Enable Performance Insights on every production instance; the default retention tier costs nothing.
- Alarm on
DBLoadrelative to vCPU count in CloudWatch, not just on CPU. - Review the top-SQL list monthly — load composition drifts long before customers complain.
- Pair it with
pg_stat_statementsor the MySQL performance schema so statement-level timing backs up the session-level picture.
The load-by-wait chart is the fastest honest answer to “why is the database slow” that AWS offers — learn to read it before the incident, not during. If nobody on your team truly owns the AWS environment end to end, that is the root of most of these problems — Stack Harbor runs AWS accounts daily under AWS Operations, and you can book an AWS review.