Cost Optimization

Right-sizing, Reserved vs On-Demand vs Spot instances, and common AWS cost pitfalls.

Why cost optimization is its own skill

AWS's pay-for-what-you-use model (covered in the introduction) is only a cost advantage if you actually stop paying for what you're no longer using. In practice, cloud bills grow through a thousand small, individually reasonable-looking decisions — an oversized instance "just in case," a snapshot nobody deleted, a load balancer left running after the project it served was shut down — none of which look alarming on their own, but which compound into significant, entirely avoidable spend over months and years. Cost optimization is the ongoing discipline of catching these before they compound, not a one-time cleanup.

Right-sizing

Right-sizing means matching an instance's (or a database's, or a container's) provisioned capacity to what the workload actually needs, based on observed usage rather than a guess made before launch. Teams very consistently over-provision "to be safe" — picking m5.2xlarge for a workload that a t3.medium handles comfortably — because it's easier to guess big than to load test properly, and the cost of guessing wrong is invisible until the bill arrives.

CloudWatch (covered on the previous page) is the tool for finding right-sizing candidates: an instance whose CPU utilization sits at 8% for weeks is a strong signal it's oversized, just as one that's pegged at 95% is a signal it's undersized.

Bash
aws cloudwatch get-metric-statistics \
  --namespace AWS/EC2 \
  --metric-name CPUUtilization \
  --dimensions Name=InstanceId,Value=i-0123456789abcdef0 \
  --start-time 2026-07-25T00:00:00Z \
  --end-time 2026-08-25T00:00:00Z \
  --period 86400 \
  --statistics Average Maximum

A month of daily averages consistently under 15-20% CPU, with maximums that never approach anywhere near 100%, is a solid case for downsizing. AWS Compute Optimizer goes a step further and analyzes exactly this kind of history automatically, recommending a specific smaller (or larger) instance type based on real observed usage rather than a manual review.

Reserved, on-demand, and Spot: three ways to pay for the same compute

EC2 (and several other services) offer fundamentally different pricing models for identical underlying hardware, trading commitment and interruption risk for a lower price:

On-Demand Reserved / Savings Plans Spot
Commitment None — pay per second/hour, stop anytime 1 or 3 year commitment None
Discount vs. On-Demand Baseline (0%) Up to ~40-72% Up to ~90%
Can be interrupted by AWS No No Yes, with a 2-minute warning, when AWS needs the capacity back
Best fit Unpredictable, short-lived, or experimental workloads Steady-state, predictable baseline load you're confident you'll run for the full term Fault-tolerant, interruptible, or stateless batch workloads
  • On-Demand is the default: no commitment, highest per-hour price, complete flexibility to stop whenever you want. Right for anything you're not yet sure will run long-term, or short-lived dev/test environments.
  • Reserved Instances / Savings Plans trade a 1- or 3-year commitment for a substantial discount off On-Demand pricing, in exchange for committing to pay for that capacity whether you use it or not (Savings Plans are the more flexible, newer variant — a commitment to a certain dollar amount of compute spend per hour, applicable across instance families, rather than a specific instance type in a specific region). The right fit is a workload you're confident will run steadily for the full commitment term — a production database, a baseline fleet of web servers that's always running regardless of traffic.
  • Spot Instances sell spare, otherwise-idle AWS capacity at a steep discount, with the catch that AWS can reclaim it with only a two-minute warning whenever that capacity is needed elsewhere. This is the right fit only for workloads that tolerate interruption gracefully — batch processing jobs, CI/CD build runners, distributed data processing (Spark, Hadoop) where a lost node just gets retried — never for a stateful workload with no tolerance for an abrupt shutdown, like a single-instance database with no replica.

A mature production account typically mixes all three: Reserved/Savings Plans covering the predictable steady-state baseline, On-Demand covering variable load above that baseline, and Spot covering anything interruption-tolerant, like batch jobs or horizontally-scaled stateless workers.

Common cost pitfalls

Beyond picking the wrong pricing model, a handful of specific mistakes account for a disproportionate share of avoidable AWS spend:

  • Unattached EBS volumes. Terminating an EC2 instance doesn't always delete its attached storage volume — a stopped or terminated instance can leave an orphaned EBS volume behind, still billed hourly, doing nothing for anyone.
  • Idle load balancers. An Application or Network Load Balancer bills by the hour and by usage, whether or not it has any healthy targets behind it — a load balancer left pointing at a decommissioned service keeps costing money indefinitely.
  • Old snapshots and AMIs. EBS snapshots and custom AMIs accumulate over time (often from automated backup schedules) and are rarely cleaned up automatically — a lifecycle policy that expires old snapshots after a defined retention window is far cheaper than manual cleanup nobody remembers to do.
  • Cross-AZ and cross-region data transfer. Data transferred between Availability Zones, and especially between Regions, is billed per GB and adds up quickly for chatty, high-throughput services that weren't architected with data locality in mind.
  • Development/staging environments running 24/7. A staging environment that's only actually used during business hours but runs around the clock is paying full price for roughly three times the hours it needs to — scheduling it to stop overnight and on weekends is a simple, high-leverage saving many teams never bother to set up.
Bash
# Find EBS volumes that aren't attached to anything
aws ec2 describe-volumes \
  --filters Name=status,Values=available \
  --query "Volumes[].{ID:VolumeId,Size:Size,Created:CreateTime}"

AWS Cost Explorer and AWS Budgets are the two built-in tools worth setting up early rather than discovering after an unpleasant bill: Cost Explorer visualizes spend broken down by service, account, or tag over time so trends and spikes are visible before they become a crisis, and Budgets can alert (via SNS, the same mechanism covered on the CloudWatch page) when spend is on track to exceed a threshold you define, well before the billing period actually closes.

Common mistakes

  • Picking an instance size "to be safe" without ever load testing or reviewing actual CloudWatch utilization months later — oversized instances are one of the most common sources of quietly wasted spend.
  • Committing to Reserved Instances or a Savings Plan for a workload whose long-term shape is still genuinely uncertain — the discount is only a saving if the commitment gets fully used; an abandoned reservation is a sunk cost with no offsetting benefit.
  • Running Spot Instances for a stateful, single-point-of-failure workload with no tolerance for a sudden two-minute-notice interruption.
  • Leaving unattached EBS volumes, idle load balancers, and stale snapshots around indefinitely instead of tagging resources by project/owner and periodically auditing for anything an owner no longer recognizes.
  • Never setting up AWS Budgets alerts — the first time cost becomes visible is the invoice at the end of the month, well after the spend already happened and any chance of catching it early is gone.