The database is the one part of the stack where a cost experiment can become a data incident, so RDS cost work should start with the levers that carry no data risk: migrating gp2 storage to gp3, buying reserved instances on the workloads that have proven stable, deleting orphaned manual snapshots, and stopping non-production databases outside working hours. Instance downsizing and provisioned-IOPS cuts come after — with measurements, not vibes. In roughly that order, most RDS bills come down noticeably without a single risky change to the production path.
What’s happening
An RDS bill has four main lines and they respond to different levers. Instance hours — the class you run, times two if Multi-AZ, times every replica. Storage and IOPS — allocated GB (not used GB: RDS bills what you allocated, and you cannot shrink allocated storage without a dump-and-restore) plus provisioned IOPS if you bought them. Backups and snapshots — automated backup storage beyond your retention window’s free allowance, and every manual snapshot forever until someone deletes it. Data transfer — mostly cross-AZ replication and cross-region replicas.
The safe levers attack these without touching data durability: gp2-to-gp3 conversion is an online storage-type change with the same durability and, at most sizes, lower cost per GB; RDS Reserved Instances are a pure billing construct with zero technical footprint; snapshot deletion touches only copies whose retention you have verified; stopping a dev instance overnight changes nothing about the data. The risky levers are the ones that change capacity: smaller instance class, fewer replicas, dropping Multi-AZ. Those are sometimes right, but they are performance and availability decisions wearing a cost hat.
What to check first
- Storage type per instance. Any gp2 volume is a candidate for gp3 at equal or better baseline performance. Check RDS > Databases > (instance) > Configuration.
- Reserved instance coverage. Stable production databases running on-demand for over a year are the single most common RDS overspend.
- Manual snapshot count and age. Automated backups expire; manual snapshots never do.
- Non-production instances running 24/7. Dev and staging databases idle 128 of 168 hours a week.
- Provisioned IOPS versus consumed IOPS. io1/io2 volumes bought for a load test three years ago are still billing.
How to verify
# Inventory: class, engine, storage type, Multi-AZ
aws rds describe-db-instances \
--query 'DBInstances[].[DBInstanceIdentifier,DBInstanceClass,Engine,StorageType,AllocatedStorage,MultiAZ]' \
--output table
# Manual snapshots, oldest first
aws rds describe-db-snapshots --snapshot-type manual \
--query 'sort_by(DBSnapshots,&SnapshotCreateTime)[].[DBSnapshotIdentifier,DBInstanceIdentifier,SnapshotCreateTime,AllocatedStorage]' \
--output table
# Is this database actually used? Connections over 30 days
aws cloudwatch get-metric-statistics --namespace AWS/RDS \
--metric-name DatabaseConnections \
--dimensions Name=DBInstanceIdentifier,Value=staging-db \
--start-time 2026-06-04T00:00:00Z --end-time 2026-07-04T00:00:00Z \
--period 86400 --statistics Maximum
# Real IOPS consumption vs what is provisioned
aws cloudwatch get-metric-statistics --namespace AWS/RDS \
--metric-name WriteIOPS \
--dimensions Name=DBInstanceIdentifier,Value=prod-db \
--start-time 2026-06-27T00:00:00Z --end-time 2026-07-04T00:00:00Z \
--period 3600 --statistics Maximum
Console: RDS > Databases > (instance) > Monitoring for the utilization graphs; Performance Insights (the seven-day retention tier is free) for whether CPU pressure is real. Storage-modification behaviour varies by engine and storage type — verify your case in the RDS documentation before scheduling anything.
The runbook
- Convert gp2 to gp3. Online modification, no downtime for the storage change itself, and gp3’s baseline (3,000 IOPS, 125 MB/s) beats small gp2 volumes whose IOPS scaled with size. Verify performance for volumes over ~1 TB where gp2’s scaled IOPS may have exceeded gp3’s baseline — provision the difference explicitly if needed.
- Delete verified-obsolete manual snapshots. Anything from decommissioned instances, pre-migration checkpoints long past, or “before the upgrade” moments from previous years. Keep whatever your retention policy genuinely requires — the strategy is laid out in the PITR and snapshot strategy article.
- Buy RDS Reserved Instances on the proven-stable. One-year, no-upfront on production instances that have run the same class for six months or more. Size-flexible within the same family and engine, so a later move from
db.r6g.xlargetodb.r6g.2xlargestill gets covered proportionally. - Stop non-production off-hours. Stopped RDS instances stop billing instance hours (storage still bills) — but AWS restarts them automatically after seven days, so the automation must re-stop, not stop once. EventBridge Scheduler plus a small Lambda handles it.
- Rightsize provisioned IOPS before instance class. If consumed IOPS peaks at a fraction of provisioned io1/io2, moving to gp3 with explicitly provisioned extra IOPS is usually cheaper and data-risk-free.
- Downsize instance class last, with evidence. p95 CPU under ~30%, no memory pressure (watch
FreeableMemoryand swap), and Performance Insights showing headroom. In Multi-AZ, the modification fails over rather than hard-stops — brief interruption, so schedule it. One size at a time. - Question replicas, do not reflexively cut them. A read replica nobody queries is pure cost; one that absorbs reporting is protecting production. Check its
ReadIOPSand connections before deciding. If you are weighing engine-level moves, the Aurora vs RDS comparison covers when the cost model itself changes.
When this is the wrong move
Never drop Multi-AZ on production to save money — that is selling your failover, and the first unplanned AZ event prices it accurately. Do not shrink allocated storage expecting a refund: RDS storage only grows, and a downsize means a full dump-and-restore migration. And skip instance-class cuts entirely on databases within sight of a traffic event (launch, campaign, seasonal peak) — a database that falls over costs more than a year of the saving.
Common mistakes
- Stopping a dev RDS once and assuming it stays stopped — the seven-day auto-restart quietly resumes billing.
- Deleting a manual snapshot that was the only copy of a decommissioned system an auditor later asks about.
- Downsizing based on average CPU while
FreeableMemorywas already near zero. - Buying three-year RIs on an engine version the team is actively planning to migrate off.
Keeping it under control
- Tag every RDS instance with environment and owner; alert on any non-prod instance running at 3 a.m.
- Review manual snapshots quarterly with an explicit keep/delete decision per snapshot.
- Track RI coverage and utilization for RDS separately from EC2 — they are different purchase programs.
- Watch
FreeStorageSpacegrowth to plan storage increases deliberately instead of over-allocating up front. - Re-check gp2 remnants after every restore or new environment — old habits recreate them.
The pattern across all of it: billing-side levers first, capacity-side levers only with measurement. 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.