Reclaiming Terabytes: How to Cut a Managed Database Bill Without Downtime
Managed databases are the cloud cost line people quietly stop looking at. Compute gets rightsized, storage on the instances gets cleaned, but the RDS, Aurora, or Azure SQL bill just grows, because a database feels too load-bearing to touch. It is not. Here is how I have cut managed database spend without a maintenance window, in the order of least risk to most. The theme throughout: databases give you more no-downtime levers than people assume, and the biggest wins are usually storage and rightsizing, not some exotic re-architecture. Start with the free win: reclaim dead storage Storage is where the surprise terabytes hide, and most of it comes off with zero downtime. - Drop what nobody reads. Old audit tables, soft-deleted rows that were never purged, expired sessions, staging data that got promoted to prod years ago. A DELETE in batches plus a purge job is the boring, safe first move. - Reclaim space after deletes. On Postgres, deleted rows leave bloat until vacuumed. Run VACUUM (and checkpg_stat_user_tables for dead tuples). On SQL Server / Azure SQL, rebuild or reorganize fragmented indexes to reclaim pages. This is where the "reclaimed terabytes" headlines actually come from. - Kill redundant indexes. Unused and duplicate indexes cost storage and slow writes. Postgres pg_stat_user_indexes (look foridx_scan = 0 ) and SQL Server's missing/unused index DMVs tell you which ones earn their keep. Dropping an unused index is online. - Right-size your storage type. On AWS, moving from gp2 to gp3 lets you provision IOPS and throughput independently and usually costs less for the same performance. The modify is applied without downtime. None of the above requires a window. It is pure hygiene, and on a neglected database it is often the single biggest line-item drop. Rightsize the instance (yes, without downtime) The reflex fear is that resizing a database means an outage. With a Multi-AZ deployment it usually does not. - Check if you are oversized first. Pull 30 days of CPU, freeable memory, and connection metrics. A database averaging single-digit CPU with plenty of free memory is a rightsizing candidate. Do not trust the instance class someone picked "to be safe" two years ago. - Use the Multi-AZ failover to your advantage. On RDS, modifying the instance class applies to the standby first, then fails over to it. The failover is seconds of blip, handled gracefully if your app retries connections (which it should). For Aurora, you resize or add readers without touching the writer's availability the same way. - Watch memory, not just CPU. The classic mistake is downsizing a database that looks CPU-idle but is memory-bound on its buffer cache. Shrink the memory and your cache hit rate falls, disk reads spike, and latency gets worse. Rightsize on the binding resource, not the convenient metric. Then the commitment lever Once the database is the right size, stop paying on-demand for something that runs 24/7. - Reserved Instances / Savings Plans for the steady baseline. A production database is the definition of a predictable, always-on workload, which is exactly what commitments are for. Reserve the baseline you know you will run for a year; leave burst on on-demand. - Do this last, not first. Never buy a reservation for an oversized instance. Rightsize, then commit, or you lock in the waste for a year. I have watched teams do these in the wrong order and regret it. The non-prod databases nobody schedules Production has to stay up. Staging, dev, and QA databases do not, and they are often the same instance class as prod "so it matches." A dev database running 24/7 for a team that uses it 40 hours a week is 75% waste. - Stop non-prod databases off-hours. RDS lets you stop an instance for up to 7 days at a time; automate a start/stop schedule so dev and staging sleep overnight and on weekends. (We schedule non-prod databases the same way we schedule everything else non-prod, that scheduling is part of what ZopNight handles for us, but a Lambda on an EventBridge cron does the crude version.) - Snapshot and delete truly idle ones. A database used once a quarter does not need to exist between uses. Snapshot it, delete the instance, restore when needed. You keep the data, you stop paying for the running instance. The order that works - Reclaim storage (deletes, vacuum/reindex, drop unused indexes, gp2 to gp3). No downtime, biggest surprise wins. - Rightsize the instance using Multi-AZ failover. Seconds of blip at most. - Schedule or snapshot non-prod databases. Free money, nobody uses them off-hours. - Buy commitments on the now-correct baseline. Last, never first. Every step above is reversible and none needs a maintenance window worth announcing. The database being "too important to touch" is exactly why it accumulates the most waste, nobody dares look. Look. What is the most storage you have ever reclaimed from a single managed database, and what was hiding in it? Mine was an audit table that had been growing unpurged since the app launched. Top comments (0)
Comments
No comments yet. Start the discussion.