Every number in this post is real. It is reproducible from AWS Cost Explorer, CloudWatch, CloudTrail, and Postgres' own statistics — and each change was recorded in an audit trail with its dollar impact and how it was verified. The theme running through all of it is simple: measure first, make the workload smaller before you resize the hardware, and never guess at a production system.
The starting point: a database on fire
The production Postgres instance was a db.m5.12xlarge — 48 vCPUs, 192 GB of RAM — pinned near 100% CPU during peak hours. Dashboards timed out. The public API returned 10-to-20-second responses. It looked like the kind of problem you throw a bigger instance at. It was the exact opposite: the database was that big precisely because nobody had ever made the workload smaller. The right move was to shrink the work first, and only then shrink the machine.
That principle turned out to apply to almost every line item on the bill. Compute, networking, streaming — again and again the story was the same: something provisioned for a worst case that never came, or left running long after it stopped being used.
Phase 1 — Make the database workload smaller
I reset pg_stat_statements to clear years of poisoned averages and let it collect a clean sample. Two dashboard queries were responsible for ~67% of all database CPU — not because the database was overloaded, but because they were written badly: the same ID list interpolated into three separate subqueries, a self-referential filter the planner could not simplify, a table joined twice, and a dead WHERE clause that always evaluated to NULL. Rewritten into clean joins, they went 4.3x and 3.3x faster. I verified correctness by diffing both versions against production row-by-row before shipping.
Two more database wins followed. A single partial index dropped the filter Lambdas' hot-path query from 3.9 seconds to 0.12 milliseconds — a ~32,000x speedup for a five-minute change. And a Redis read-through cache in front of the dashboard endpoint (which an automated client was polling on a fixed loop) took the hit rate to 98%. Finally, a batched retention drain removed ~86 million posts older than the 90-day window that had never been purged — done with foreign keys temporarily dropped and restored NOT VALID to avoid an hours-long lock.
Phase 2 — Right-size the compute
With CPU at fractions of a percent, the 48-core database was absurd. I moved it to Graviton (ARM) and rightsized it twice as the true steady state revealed itself: m5.12xlarge (48 vCPU) → m6g.4xlarge (16 vCPU) → m6g.2xlarge (8 vCPU). Graviton is meaningfully cheaper than the x86 equivalent and, for a steady Postgres workload, performs identically or better. The database RDS line dropped from ~$4,210/month to ~$1,580.
Then the ECS Fargate fleet. The portal API was running 31 tasks of 2 vCPU / 4 GB each — "scale by terror" sizing from an earlier incident. Fleet CPU sat at 0.3-15%. I took it to a single task, which held the load comfortably at 0.5-7% CPU. That one change was worth ~$1,200/month. Dev environments got the same treatment: over-sized services scaled down, and one under-sized batch service scaled up to stop it throttling at 95-98% CPU daily.
Phase 3 — Fix the expensive misconfigurations
The single most surprising line item was cross-AZ data transfer. The portal API's load balancer was moving 54.7 TB/month with cross-zone balancing on, but the service ran as one task in one random availability zone — so roughly two-thirds of all requests crossed AZ boundaries at $0.01/GB. That was ~$365/month of pure networking waste. The fix was to run three tasks; Fargate spreads them across us-east-1a/b/c automatically, so every load-balancer AZ now has a local target and the cross-AZ traffic evaporated.
That rollout then exposed a second misconfiguration: after going multi-AZ, the service chronically ran 9-12 tasks instead of 3, at 7% CPU. The autoscaling target was set to 90 requests/minute per task, but steady traffic was ~1,260 req/min — so the policy demanded 14+ tasks at all times, regardless of load. Raising the target to 500 req/min and shortening the scale-in cooldown settled it back to 3 tasks and recovered ~$216/month that would otherwise have been eaten by the multi-AZ change.
Phase 4 — Kill the dead and the dormant
With the live system efficient, the remaining money was in things that were simply still running long after anyone needed them. This is where a careful audit — reading two years of CloudWatch history, not thirty days — matters, because "idle for a month" and "genuinely dead" are very different claims.
- Kinesis: 5 streams with zero traffic for 3+ months (test streams and two legacy Prod streams whose last blip was months prior), plus their python2.7 zombie consumer. ~$145/month.
- AWS Transfer Family (SFTP): a server whose last real file activity was September 2023 — 20 months idle. The only recent "traffic" was brute-force auth attempts from a foreign IP. Deleted; its 6 MB of historical files preserved in S3. ~$216/month.
- Two orphan load balancers in a legacy VPC: 0 requests in 7 days, empty target groups, already replaced by newer ones with Route53 pointed elsewhere. ~$50/month.
- A forgotten SageMaker Studio domain: $0/month while idle, but a JupyterServer app left InService — a dormant bomb. A prior month had a $2,500 spike from someone opening the console and running notebooks. Verified zero usage across all 48 org repos before deleting it.
- A fixed support plan the team no longer needed, deactivated by decision. ~$491/month.
The result
Broken down by service, the biggest movers were RDS ($4,210 → ~$1,580), ECS Fargate ($1,501 → ~$130), and load balancing / cross-AZ ($1,253 → ~$300), with a long tail of cleanup — Kinesis, Transfer Family, support plan, orphaned resources — adding up to hundreds more. And crucially, none of this was "turn things off and hope." The platform is faster, more reliable, and has more headroom than it did at four times the cost. The database that was on fire now runs at ~0.3% utilization.
What I would tell anyone doing this
- Measure before you touch anything. pg_stat_statements, Cost Explorer, and CloudWatch told the whole story — no guessing required.
- Make the workload smaller before you resize the hardware. Resizing a bloated, un-cached system just buys a more expensive version of the same problem.
- The biggest surprises are rarely in the obvious line items. Cross-AZ data transfer, a mis-set autoscaling target, a 20-month-idle SFTP server — and, above all, an entire AWS region idle for eight years at $1k/month — were worth more than most of the "real" infrastructure combined.
- Read the long history, not the last 30 days. "Idle this month" is not "dead." One Lambda had done 700k invocations two months before it went quiet.
- Verify correctness and dependencies before every deletion. Diff query outputs; grep every repo; check Terraform. Rightsizing even surfaced a customer-facing enforcement bug that had been hidden by over-provisioning.
- The cheapest infrastructure is the work you never make the system do.
