Your RDS Bill Is High. The Database May Not Be the Problem

Both moves treat the instance as the problem. Usually it's the symptom. A database gets oversized because something upstream asks it to do too much: a query that runs on every page view, a few hundred idle connections, a table that has only ever grown, a report someone runs at noon. Someone raised the instance class to make the pain stop, and the bill kept the new number.
In our last cost post, Xperiencify's AWS bill went from $10,000 to $2,000 a month in 14 days, and database optimization was one of the two levers. This is the diagnostic behind that lever, written so you can run it on your own account. It's the general method, not a list of Xperiencify's findings.
Why the instance is the wrong place to start
An RDS bill is several bills wearing one name: instance hours, allocated storage, provisioned IOPS, backups past a free allowance, data transfer and, on an old enough engine version, Extended Support. Each part grows for its own reason, and a smaller instance fixes exactly one of them.
The other trap is commitment. Reserved instances cut the hourly rate, and they're worth buying. Buy them after the workload is fixed. Otherwise you lock the oversized instance in for one or three years and call the discount a saving.
Step 1: Split the line before you touch anything
In Cost Explorer, filter to RDS and group by usage type. Then match each part that grew to its usual cause:
| If this part grew | Look here first |
|---|---|
| Instance hours | A class raised during an incident and never lowered, or a read replica nobody reads from |
| Storage | Tables that only grow, bloat, unused indexes, autoscaling that stepped up once and stayed |
| Provisioned IOPS | io1 or io2 volumes provisioned for a peak. AWS bills them "regardless of IOPS consumed" |
| Backup storage | Manual snapshots from environments that no longer exist |
| Extended Support | An engine major version past the end of standard support |
Two of these are housekeeping. Backup storage is free up to your account's total provisioned database storage in the region (RDS FAQ), so a backup charge means snapshots piled up past that. Extended Support starts the day after your major version leaves standard support and is billed per vCPU-hour (RDS for PostgreSQL pricing). The fix is an engine upgrade, one of the few cost fixes that also closes security gaps.
Step 2: Find out what the instance is sized for
Open CloudWatch and look at 30 days, not one afternoon: CPU, freeable memory, connections, read and write IOPS. An instance is sized for one of four things, and you need to know which before you change it.
- CPU. Sustained high CPU usually means a handful of expensive queries. Step 3 finds them.
- Memory. The working set doesn't fit in cache, so reads go to disk. Often a bloated table or a never-used index is taking the room.
- Connections. In PostgreSQL every connection is its own server process with its own memory. Serverless functions and autoscaled pods each open a pool, so on launch day the count jumps into the hundreds and someone raises the instance class to survive it. A pooler such as PgBouncer or RDS Proxy fixes the cause and usually costs less than the next size up.
- One day a year. It was sized for the biggest launch and idles the rest of the time.
If CPU sits low for weeks and memory is comfortable, the instance is sized for history.
Which part of your RDS bill is the real problem?
Paste your RDS cost lines and a few CloudWatch numbers into ChatGPT and get a first read on what your database is actually sized for.
“|”
Step 3: Rank queries by total time, not by the slowest one
On PostgreSQL, turn on pg_stat_statements if it isn't on already. On MySQL, use the performance schema. Sort by total execution time. The query at the top is rarely the scary four-second report. It's the 30-millisecond query that runs on every request.
The same patterns show up in most codebases:
- An ORM loop that loads a list, then issues one query per row.
- A count over a large table to draw a badge or a pager on every page view.
- Polling: a dashboard or a job queue asking the database "anything new?" every second.
- Reports and exports running against the primary at peak hours.
- A missing index that turns a lookup into a full scan, which then shows up as CPU and as read IOPS.
Nearly all of these fixes live in application code: an index, a join instead of a loop, a cached value, a job moved to the night. None of them needs a bigger instance.
Step 4: Decide how long data lives
Some tables only grow: events, audit logs, sessions, job history, notifications. Nobody decided to keep them forever. Nobody decided not to. Give each one a retention rule. Archive what you must keep to S3 and delete the rest in small batches. In PostgreSQL, deleted rows leave dead space until vacuum reclaims it, so check bloat as well as row counts. Drop indexes that are never scanned. They take storage and slow down every write.
There's a catch. Deleting data doesn't shrink allocated storage. Until late 2024, shrinking meant a new instance and a manual migration. AWS now supports shrinking storage through a Blue/Green deployment, so it's a planned switchover rather than a project.
Step 5: Resize last, to measured load
Only now pick the instance class. Size it for your spikiest hour after the fixes, with headroom. Make the change in infrastructure code so it's reviewed and can be rolled back, schedule it away from launches, and watch latency and errors for a full business cycle before the next one.
At Xperiencify the database lever sat inside the same 14 days as the cleanup of unused resources. No migration, no new database engine.
Is the database the biggest line on your AWS bill?
We run this diagnostic in your AWS account and hand you a change plan that fixes the causes first and resizes last. Flat price, one senior engineer. Send your email and we'll set up a 30-minute call within 24 hours.
When the database really is the problem
Sometimes the load is real: CPU tracks active users and the top queries are already tight. Then the levers change:
- Move read-heavy traffic and reporting to a read replica.
- Put a cache in front of data that's read far more often than it changes.
- Partition the biggest tables by time, so old partitions can be archived whole.
- On Aurora, check the I/O share of your spend. AWS says Aurora I/O-Optimized saves up to 40% when I/O charges exceed 25% of total Aurora spend.
- Buy reserved instances once the size has held steady for a few months.
A move to another database comes last. It's the one that takes months.
The RDS check
Three or more "no" answers usually mean the bill can fall without a bigger or different database.
- Can you say how much of the RDS line is instance, storage, IOPS, backups and support fees?
- Do you know whether the instance is sized for CPU, memory, connections or one launch day?
- Can you name your top 10 queries by total execution time?
- Is there a connection pooler between the app and the database?
- Does every table that only grows have a retention rule?
- Is every engine version inside standard support?
- Have manual snapshots from retired environments been deleted?
- Was the instance class last changed from measured load, not during an incident?
Related reading
- We cut an AWS bill without rewriting the product: the $10,000 to $2,000 case this guide follows
- How we stopped clients complaining "site is down again" with AWS, Kubernetes and ArgoCD: the platform the cost work ran on
- One day your site will go down. Do you know who's going to fix it?: who watches the database after the change
The service: Cloud Engineering. If the database is one of several fires: costs exploding and database is a mess.
Enjoyed this article? Share it with others
Related Posts

We Cut an AWS Bill Without Rewriting the Product
Xperiencify's AWS bill went from $10,000 to $2,000 a month in 14 days. Nothing was migrated and nothing was rewritten. We removed what nobody used and made the database do less work. Here is the method and the checklist we use.

How We Stopped Clients Complaining Site is Down Again with AWS Kubernetes and ArgoCD
Tired of clients writing site is down again? We fixed this for SaaS teams with Gitops + AWS + Kubernetes + ArgoCD = 99.99% uptime and an AWS bill cut from $10,000 to $2,000 a month.