Skip to content

RDS cost optimization: the levers that do not risk the database

AWS RDS cost optimization without endangering the data: gp3 storage migration, safe instance rightsizing, reserved instances, snapshot hygiene, and stopping non-prod databases.

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

  1. 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.
  2. 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.
  3. 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.xlarge to db.r6g.2xlarge still gets covered proportionally.
  4. 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.
  5. 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.
  6. Downsize instance class last, with evidence. p95 CPU under ~30%, no memory pressure (watch FreeableMemory and 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.
  7. Question replicas, do not reflexively cut them. A read replica nobody queries is pure cost; one that absorbs reporting is protecting production. Check its ReadIOPS and 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 FreeableMemory was 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 FreeStorageSpace growth 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.