RDS overprovisioning is the cost problem that doesn't feel like a problem. The database works. Queries are fast. Nobody is getting paged. The bill is just a bit higher than it needs to be, month after month, because the instance was sized for a load that never materialized or has long since been replaced by a different architecture.
The insidious part is that RDS pricing scales with instance class in a way that makes oversizing expensive at any tier. A db.r6g.xlarge (4 vCPU, 32 GB RAM) runs around $700/month in us-east-1 on-demand. A db.r6g.large (2 vCPU, 16 GB RAM) runs around $350/month. If your workload only uses 4% CPU on the xlarge, you almost certainly don't need those 4 vCPU or 32 GB RAM, and the right-sized instance is half the price. For a team running 6-8 production databases, that gap compounds quickly.
Why overprovisioning happens and persists
The provisioning decision happens once, usually during a feature launch or infrastructure setup sprint. The engineer responsible picks an instance class based on expected load, with a safety margin for peaks. This is reasonable engineering judgment under uncertainty. You don't want to be debugging database performance during a product launch.
The problem is that the expected load often doesn't arrive, or the feature gets replaced with something lighter, or the caching layer added later reduces database read load significantly. The instance class that made sense at provisioning time doesn't get revisited because revisiting it requires:
- Knowing which RDS instances exist and their current utilization
- Having the context to understand whether low utilization is expected (dev/staging environment, scheduled batch workload) or unexpected (overprovisioned production instance)
- Having a low-risk path to right-size (modify the instance class, which involves a reboot window)
- Someone owning the task of doing this check regularly
In practice, none of these conditions are routinely met. Utilization data lives in CloudWatch, which engineers check when something is slow, not when they're looking for things to downsize. Context about what a database instance is for lives in tribal knowledge. Instance class modification involves a maintenance window and coordination with the team that owns the database. And nobody is assigned to run a monthly RDS right-sizing review.
The utilization thresholds that matter
Not all low-utilization RDS instances are overprovisioned. The distinction matters for avoiding false positives in waste detection.
| Utilization pattern | Likely explanation | Right-sizing candidate? |
|---|---|---|
| Avg CPU <5%, max CPU <15% | Significantly overprovisioned or very low-traffic database | Yes, investigate |
| Avg CPU 5-20%, max CPU <40% | Moderately overprovisioned, some headroom to shrink | Worth reviewing |
| Avg CPU 20-50%, max CPU <70% | Reasonable utilization with adequate headroom | No action needed |
| Avg CPU <5% but with regular spikes to 60%+ | Bursty workload (batch jobs, cron-driven analytics) | No, required headroom |
| Consistent <5% CPU AND FreeableMemory >80% of total | Almost certainly overprovisioned across both compute and memory | Strong candidate |
The CPU-only view misses memory. Some RDS workloads, particularly those with large working sets that benefit from buffer pool caching (InnoDB on MySQL/Aurora, shared_buffers on Postgres), need a large instance for the memory even if CPU is light. Before flagging an instance for right-sizing, we check FreeableMemory as well. An instance running at 4% CPU with 82% of its memory free is almost certainly oversized on both dimensions.
A concrete example
We worked through this with a small engineering team running a B2B analytics product. Their Finopscraft waste detection flagged a db.r6g.xlarge they called rds-prod-historical-data. The instance had been running for 8 months. Average CPU was 3.8% over the trailing 30 days. Max CPU (99th percentile) was 11% over the same period. FreeableMemory averaged 27 GB of 32 GB total, meaning the instance was using less than 16% of its memory.
When the team looked into it, the historical data database was originally sized to handle backfill operations that had been running when it was first provisioned. The backfill finished after two weeks and the instance was never downsized. For the subsequent 7.5 months, they had been paying $700/month for a database running at sub-5% utilization. Total: roughly $5,250 in unnecessarily spent compute.
They downsized to a db.r6g.large: half the RAM, half the vCPU, half the price. The instance handled the workload without issue. They ran the modification during a low-traffic window on a Sunday morning; the failover to the new instance class took about 5 minutes. The savings from that single right-sizing operation covered a significant fraction of their Finopscraft subscription cost for the year.
The right-sizing process: what to check before you resize
CPU and memory utilization tell you most of what you need, but a few additional checks reduce the risk of right-sizing a database that actually needs the headroom.
Check for scheduled batch jobs. A database that runs light for 27 days and then gets hammered by a month-end batch job might not be safely downsizable. Pull the max CPU over a 90-day window, not just the trailing 30 days, to catch periodic patterns.
Check storage IOPS. Some RDS workloads are IOPS-bound rather than CPU- or memory-bound. An instance with low CPU but consistently near the IOPS limit for its storage tier isn't a right-sizing candidate; it's a storage configuration issue.
Check database connections. Max connections is instance-class-dependent (roughly, higher the class, more max connections). If an application is occasionally hitting connection limits, downsizing the instance might make the connection constraint worse. Check DatabaseConnections metrics in CloudWatch before reducing instance class.
Check replication lag for read replicas. If the instance has read replicas and is already running them close to their limits, reducing the primary instance class can increase replication lag under load.
None of these checks are complex, but they need to happen before every resize. We surface the relevant CloudWatch metrics in the waste flag detail view so the team doing the remediation has the context without having to pull it separately.
RDS Reserved Instances: the interaction with right-sizing
If you have RDS Reserved Instances, right-sizing has an additional consideration. RI pricing is locked to the instance class at purchase time. If you right-size a db.r6g.xlarge that you have a 1-year RI for to a db.r6g.large, you're paying for RI coverage on the larger instance but running the smaller one on-demand. The RI becomes waste unless you sell it on the AWS Reserved Instance Marketplace or wait for it to expire.
This doesn't mean you shouldn't right-size. Depending on how many months remain on the RI and the on-demand vs. RI price differential, the math may still favor right-sizing immediately. But it needs to be calculated explicitly, not assumed. We're not saying RI commitments make right-sizing inadvisable. We're saying the cost analysis needs to include the RI implication, not just the compute difference.
The Finopscraft waste flag for RDS overprovisioning includes RI status for the flagged instance, so the team making the right-sizing decision has the full picture in one place.
Preventing the problem: rightsizing at provision time
Detection and remediation address existing overprovisioning. Prevention means building a review step into your provisioning workflow before the instance is launched, not six months later.
A practical approach: add a commented checklist to your RDS Terraform module that requires the provisioner to specify expected peak CPU and memory for the workload, and map that to an instance class with 2x headroom (not 4x). Something like:
# Instance class selection guide:
# Expected peak CPU <25% on the selected class: consider one size down
# Expected peak memory use <40% of class RAM: consider one size down
# Batch jobs with >50% CPU spikes: maintain 2x headroom on the base class
# variable "instance_class" { default = "db.t3.medium" } # Start small, resize up
This doesn't prevent every overprovisioning decision, but it makes the reasoning explicit and documents the original expectations, which is useful context when the same instance gets flagged for low utilization six months later.
Right-sizing RDS is one of the most consistent sources of recoverable cloud cost we've found across accounts. The instances are expensive, they're long-lived, and utilization tends to drift downward over the product lifecycle. Making this detection automated and the remediation workflow low-friction is worth the setup time. The savings recur every month until the instance is next resized.