These rules cover the most common sources of unnecessary RDS spend: outdated instance classes, idle instances, missing reserved coverage, non-Graviton families, low utilization, extended support charges, orphaned snapshots, and excessive Performance Insights retention.
| Rule ID | Scan Type | Name |
|---|---|---|
| CLDBRN-AWS-RDS-1 | Discovery and IaC | RDS Instance Class Not Preferred |
| CLDBRN-AWS-RDS-2 | Discovery | RDS DB Instance Idle |
| CLDBRN-AWS-RDS-3 | Discovery | RDS DB Instance Missing Reserved Coverage |
| CLDBRN-AWS-RDS-4 | Discovery and IaC | RDS DB Instance Without Graviton |
| CLDBRN-AWS-RDS-5 | Discovery | RDS DB Instance Low CPU Utilization |
| CLDBRN-AWS-RDS-6 | Discovery and IaC | RDS DB Instance Unsupported Engine Version |
| CLDBRN-AWS-RDS-7 | Discovery | RDS Snapshot Without Source DB Instance |
| CLDBRN-AWS-RDS-8 | IaC | RDS Performance Insights Extended Retention |
CLDBRN-AWS-RDS-1
RDS Instance Class Not Preferred
Scan type: Discovery and IaC
What it checks
Flags RDS DB instances using older-generation instance classes. The preferred classes offer better performance per dollar and are AWS's recommended choices for new and existing workloads.
Preferred instance classes:
| Class | Best For |
|---|---|
db.m8g | General-purpose workloads (Graviton4) |
db.m8gd | General-purpose with local NVMe (Graviton4) |
db.m8i | General-purpose workloads (Intel x86) |
db.m7i | General-purpose workloads (Intel x86) |
db.m7g | General-purpose workloads (Graviton3) |
db.r8g | Memory-intensive workloads (Graviton4) |
db.r8gd | Memory-intensive with local NVMe (Graviton4) |
db.r8i | Memory-intensive workloads (Intel x86) |
db.r7i | Memory-intensive workloads (Intel x86) |
db.r7g | Memory-intensive workloads (Graviton3) |
db.t4g | Dev/test and burstable workloads (Graviton2) |
Non-preferred classes that trigger findings: db.m1, db.m3–db.m6*, db.r3–db.r6*, db.t2–db.t3. Instance families not in either list (e.g. db.m2) are treated as unclassified and do not trigger a finding.
Why it matters
Older-generation RDS instance classes are less performant and often more expensive than their current-generation equivalents. Graviton-based classes (db.m8g, db.r8g, db.t4g) provide 20-35% better price/performance than equivalent Intel x86 classes.
What triggers a finding
The DB instance class family matches one of the non-preferred generations listed above.
How to remediate
Modify the DB instance to use a preferred class. For production instances, use a Multi-AZ failover to minimize downtime:
aws rds modify-db-instance \
--db-instance-identifier my-db \
--db-instance-class db.m8g.large \
--apply-immediately
For Graviton migrations, verify engine compatibility first — most engines on RDS support Graviton, but check the specific engine version and region availability.
IaC resources checked
| IaC Tool | Resource Type |
|---|---|
| Terraform | aws_db_instance |
| CloudFormation | AWS::RDS::DBInstance |
CLDBRN-AWS-RDS-2
RDS DB Instance Idle
Scan type: Discovery
What it checks
Flags RDS DB instances that have had zero database connections over the past 7 days. An instance with no connections is not serving any application traffic and is a candidate for deletion or suspension.
Why it matters
RDS instances are billed by the hour for provisioned compute and storage, regardless of whether any application is connected. An idle db.m7i.large Multi-AZ instance costs over $300/month. Development and staging databases that are forgotten after a project ends are a common source of unnecessary RDS spend.
What triggers a finding
maxDatabaseConnectionsLast7Days is 0.
How to remediate
- Verify no application is using the instance (check application logs, not just the CloudWatch metric)
- If truly idle, choose one of:
- Stop the instance: RDS can be stopped for up to 7 days at a time. Storage charges continue but compute stops.
- Delete the instance: Take a final snapshot, then delete.
- Migrate to Aurora Serverless v2: For intermittent workloads, Aurora Serverless v2 scales to zero ACUs when idle, eliminating compute cost between bursts.
# Create final snapshot and delete
aws rds create-db-snapshot \
--db-instance-identifier my-db \
--db-snapshot-identifier my-db-final-snapshot
aws rds delete-db-instance \
--db-instance-identifier my-db \
--skip-final-snapshot
CLDBRN-AWS-RDS-3
RDS DB Instance Missing Reserved Coverage
Scan type: Discovery
What it checks
Flags RDS DB instances that have been running for at least 180 days without matching active reserved instance coverage. The rule matches instances to reservations by region, instance class, Multi-AZ configuration, and normalized engine (Aurora MySQL maps to mysql, Aurora PostgreSQL to postgres). Wildcard engine coverage is also consumed.
Why it matters
RDS Reserved Instances offer 30-60% savings over on-demand pricing depending on the term and payment option. An instance running on-demand for 6+ months is a strong candidate for a reservation. The longer you wait, the more savings you leave on the table.
What triggers a finding
All of the following must be true:
- Instance status is
available - Instance has been running for 180 or more days
- No active reserved instance matches the instance's
accountId:region:instanceClass:multiAz:normalizedEnginecombination
How to remediate
- Review the flagged instance's class, engine, and Multi-AZ configuration
- Check AWS Cost Explorer RI recommendations for RDS
- Purchase a matching reserved instance:
aws rds purchase-reserved-db-instances-offering \
--reserved-db-instances-offering-id <offering-id> \
--db-instance-count 1
CLDBRN-AWS-RDS-4
RDS DB Instance Without Graviton
Scan type: Discovery and IaC
What it checks
Flags RDS DB instances using non-Graviton instance families when a Graviton-based equivalent exists. The review set includes db.m5, db.m5d, db.m6i, db.m6id, db.m6in, db.m7i, db.m8i, db.r5, db.r5b, db.r5d, db.r6i, db.r6id, db.r6in, db.r7i, db.r8i, db.t2, and db.t3.
Why it matters
Graviton-based RDS classes (db.m8g, db.r8g, db.t4g) offer 20-35% better price/performance than their Intel equivalents. For most database engines, the migration is straightforward and doesn't require application changes.
What triggers a finding
The instance class family is in the Graviton review set and is not already using a Graviton family.
How to remediate
Modify the instance to use a Graviton class. Test with a read replica first if you want to validate compatibility:
aws rds modify-db-instance \
--db-instance-identifier my-db \
--db-instance-class db.r8g.large \
--apply-immediately
Check engine version compatibility, as some older engine versions don't support Graviton. Upgrade the engine version first if needed.
IaC resources checked
| IaC Tool | Resource Type |
|---|---|
| Terraform | aws_db_instance |
| CloudFormation | AWS::RDS::DBInstance |
CLDBRN-AWS-RDS-5
RDS DB Instance Low CPU Utilization
Scan type: Discovery
What it checks
Flags available RDS DB instances whose 30-day average CPU utilization stays at or below 10%. Consistently low CPU suggests the instance is over-provisioned for its workload.
Why it matters
An db.r7i.xlarge running at 5% average CPU is paying for 20x more compute than it needs. Right-sizing to a smaller instance class, or moving to Aurora Serverless v2 for variable workloads, can cut compute costs significantly with no impact on application performance.
What triggers a finding
Both conditions must be true:
- Instance status is
available - The 30-day average CPU utilization is at or below 10%
How to remediate
- Verify that the low CPU isn't masking a memory or I/O bound workload (check
FreeableMemoryandReadIOPS/WriteIOPSmetrics) - If the instance is genuinely over-provisioned, downsize:
aws rds modify-db-instance \
--db-instance-identifier my-db \
--db-instance-class db.r8g.medium \
--apply-immediately
- For workloads with highly variable traffic, consider Aurora Serverless v2 which scales compute automatically
CLDBRN-AWS-RDS-6
RDS DB Instance Unsupported Engine Version
Scan type: Discovery and IaC
What it checks
Flags RDS DB instances running MySQL 5.7 or PostgreSQL 11, which are past their standard support end-of-life and now incur extended support charges from AWS.
Why it matters
AWS charges extended support fees for engine versions past their standard EOL date. For MySQL 5.7, this adds $0.0725 per vCPU-hour in Year 1, doubling in Year 2. For PostgreSQL 11, the same pricing applies. On a Multi-AZ db.r7i.xlarge (4 vCPUs), that's an extra ~$210/month in Year 1 on top of the regular instance cost.
What triggers a finding
Either of these conditions:
- Engine is
mysqland version starts with5.7 - Engine is
postgresand version starts with11
How to remediate
Upgrade to a supported major version. For MySQL, the upgrade path is 5.7 -> 8.0. For PostgreSQL, 11 -> 14 or higher:
aws rds modify-db-instance \
--db-instance-identifier my-db \
--engine-version 8.0.39 \
--allow-major-version-upgrade \
--apply-immediately
Test the upgrade on a snapshot restore first. Review the MySQL 8.0 or PostgreSQL 14+ breaking changes that may affect your application queries.
IaC resources checked
| IaC Tool | Resource Type |
|---|---|
| Terraform | aws_db_instance |
| CloudFormation | AWS::RDS::DBInstance |
CLDBRN-AWS-RDS-7
RDS Snapshot Without Source DB Instance
Scan type: Discovery
What it checks
Flags RDS snapshots older than 30 days whose source DB instance no longer exists. Orphaned snapshots from deleted instances often accumulate indefinitely because nobody remembers to clean them up.
Why it matters
RDS snapshot storage costs $0.095 per GB-month. A 500 GB snapshot from a deleted production instance costs ~$47/month and will keep billing forever unless someone explicitly deletes it. Teams that frequently create and delete development databases are especially prone to snapshot accumulation.
What triggers a finding
Both conditions must be true:
- The snapshot's source
dbInstanceIdentifierdoes not match any currently active RDS instance in the same account and region - The snapshot's
snapshotCreateTimeis more than 30 days in the past
How to remediate
- Verify the snapshot isn't needed for compliance or disaster recovery
- If it's no longer needed, delete it:
aws rds delete-db-snapshot \
--db-snapshot-identifier my-old-snapshot
- For long-term archival, export the snapshot to S3 first (cheaper storage) and then delete the RDS snapshot:
aws rds start-export-task \
--export-task-identifier my-export \
--source-arn arn:aws:rds:us-east-1:123456789012:snapshot:my-old-snapshot \
--s3-bucket-name my-archive-bucket \
--iam-role-arn arn:aws:iam::123456789012:role/rds-export-role \
--kms-key-id alias/my-key
CLDBRN-AWS-RDS-8
RDS Performance Insights Extended Retention
Scan type: IaC
What it checks
Flags DB instances that enable Performance Insights with retention beyond the included 7-day free period. Extended retention (up to 24 months) incurs additional charges.
Why it matters
Performance Insights includes 7 days of data retention at no extra cost. Extending retention to 731 days (24 months) costs $2.48/vCPU-month for instances outside the free tier. A db.r6g.4xlarge with 16 vCPUs would pay ~$39.68/month for extended retention. Unless you need long-term performance trend analysis, the free 7-day window is sufficient for most troubleshooting.
What triggers a finding
performanceInsightsEnabled is true AND performanceInsightsRetentionPeriod is a number greater than 7.
How to remediate
Set performance_insights_retention_period to 7 in Terraform, or set PerformanceInsightsRetentionPeriod to 7 in CloudFormation. Only extend retention if you have a specific need for long-term performance data.
IaC resources checked
| IaC Tool | Resource Type |
|---|---|
| Terraform | aws_db_instance |
| CloudFormation | AWS::RDS::DBInstance |
See Also
- CLI discover command — scan live RDS instances
- CLI scan command — scan IaC templates for RDS issues
- SDK Reference — run scans programmatically