Cost optimization

Pricing models, right-sizing, tiering, visibility.

Advanced25 min · lesson 14 of 15

An AWS (Amazon Web Services) account bills you like a hotel minibar. Every item is metered on its own, nobody stops you when you reach for the peanuts, and the itemized bill turns up long after you have eaten them. Cost Optimization is one of the six pillars of the Well-Architected Framework, AWS's checklist of what good design looks like, and it is the habit of reading that meter constantly and engineering the total down. In the cloud, your architecture choices *are* your spending choices. An oversized server, a forgotten disk, a log bucket that never moves old data to cheaper storage: each one shows up on next month's invoice. Earlier lessons covered the pricing mechanics, including EC2 (Elastic Compute Cloud) purchase options, S3 (Simple Storage Service) storage classes and consolidated billing. This lesson covers the practice. See where the money goes, resize on real data, commit only to the baseline you can prove, delete the waste, and set tripwires. All of it runs from the CLI (command line interface).

Follow the money with Cost Explorer

You cannot cut a bill you cannot read, so the loop starts with Cost Explorer, the service that rolls every metered charge into one queryable dataset. From the command line it answers to aws ce. Its default measure is *unblended cost*, which is what each line item actually charged you on the day. Compare that with *amortized* cost, which takes any upfront commitment payment and smears it evenly across the month. Two things to know before your first query. The data trails reality by up to 24 hours. And while the console view is free, every Cost Explorer API (application programming interface) request costs $0.01, which is nothing for a few ad hoc questions and real money if a dashboard polls it every minute.

spend by service, last full month
aws ce get-cost-and-usage \
--time-period Start=2026-06-01,End=2026-07-01 \
--granularity MONTHLY \
--metrics UnblendedCost \
--group-by Type=DIMENSION,Key=SERVICE
# {
# "ResultsByTime": [{
# "TimePeriod": { "Start": "2026-06-01", "End": "2026-07-01" },
# "Groups": [
# { "Keys": ["Amazon Elastic Compute Cloud - Compute"],
# "Metrics": { "UnblendedCost": { "Amount": "4312.5518", "Unit": "USD" } } },
# { "Keys": ["Amazon Relational Database Service"],
# "Metrics": { "UnblendedCost": { "Amount": "1874.2003", "Unit": "USD" } } },
# { "Keys": ["Amazon Simple Storage Service"],
# "Metrics": { "UnblendedCost": { "Amount": "912.7741", "Unit": "USD" } } },
# { "Keys": ["AmazonCloudWatch"],
# "Metrics": { "UnblendedCost": { "Amount": "388.1200", "Unit": "USD" } } }
# ],
# "Estimated": false
# }]
# }

The biggest line wins your attention. EC2 is over half of this bill, so that is where right-sizing starts. To pin spend on the team that caused it, group by a cost allocation tag instead: --group-by Type=TAG,Key=team. A tag only becomes a billing dimension after you *activate* it in the Billing console, and only for charges that happen after you flip that switch, so tag early. For the exam, keep the three visibility tools straight. Cost Explorer analyzes and forecasts. Budgets alerts you when a threshold is crossed. The Cost and Usage Report (set up through Data Exports these days) is the raw, most detailed feed, dropped into an S3 bucket so Athena, the query service that reads files with SQL, can dig through it.

Right-size on evidence, not on nerves

Most oversized servers get bought the way people rent moving vans. You take the biggest one because a second trip would be embarrassing, then you drive it half empty. Right-sizing means matching an instance to what it actually uses instead of to how nervous its owner felt on launch day. The measurements come from Compute Optimizer, a free service that runs machine learning over your last 14 days of CloudWatch metrics (CloudWatch is the built-in AWS monitoring service; 93 days if you pay for enhanced infrastructure metrics) and sorts every instance into OPTIMIZED, OVER_PROVISIONED, or UNDER_PROVISIONED, each with a concrete replacement to move to. It is opt-in. One command switches it on, then you wait. It needs at least 30 hours of metric data inside the lookback window, and the analysis itself can take up to 24 hours.

compute optimizer: find over-provisioned instances
# One-time opt-in (run --include-member-accounts from the org management account)
aws compute-optimizer update-enrollment-status --status Active
# Over-provisioned instances with the top-ranked replacement
aws compute-optimizer get-ec2-instance-recommendations \
--query 'instanceRecommendations[?finding==`OVER_PROVISIONED`].{arn:instanceArn,current:currentInstanceType,suggested:recommendationOptions[0].instanceType,maxCpuPct:utilizationMetrics[0].value}'
# [
# {
# "arn": "arn:aws:ec2:us-east-1:111122223333:instance/i-0f9d8e7c6b5a43210",
# "current": "m5.2xlarge",
# "suggested": "m6i.large",
# "maxCpuPct": 9.4
# }
# ]

Notice the word *peaked*. That machine never once climbed above 9.4% CPU (central processing unit) use in two whole weeks. Moving it from m5.2xlarge to m6i.large cuts that line by roughly 75% and lands on a newer chip generation at the same time. One caveat before you act on any recommendation: CloudWatch cannot see memory use unless the CloudWatch agent is installed on the machine, so Compute Optimizer is blind to memory pressure by default. Install the agent first if the workload is memory-hungry. Right-sizing also spends your spare headroom, which is only safe because Auto Scaling (covered earlier) catches the spikes. Size for the normal day. Scale for the odd one.

Commit to the floor: Savings Plans

Once the boxes are the right size, go after the *rate* you pay for the ones that never switch off. A Savings Plan works like a phone contract. You promise to spend a fixed number of dollars per hour on compute for one or three years, and AWS cuts your rate by up to 72% in return. The exam wants you to tell the flavors apart. A Compute Savings Plan follows your usage wherever it goes, across instance families, across regions, and onto Fargate and Lambda (the two services that run containers and code without you managing servers), for up to 66% off. An EC2 Instance Savings Plan pins you to one instance family in one region and pays the deeper 72% for that loss of freedom. Reserved Instances, usually shortened to RIs, are the older version of the same idea and are mostly superseded, with one exception worth memorizing: a *zonal* RI is the only discount that also reserves capacity inside a specific Availability Zone. Do not guess the number. Ask Cost Explorer to work it out from your own history.

let AWS size the commitment
aws ce get-savings-plans-purchase-recommendation \
--savings-plans-type COMPUTE_SP \
--term-in-years ONE_YEAR \
--payment-option NO_UPFRONT \
--lookback-period-in-days THIRTY_DAYS \
--query 'SavingsPlansPurchaseRecommendation.SavingsPlansPurchaseRecommendationSummary'
# {
# "EstimatedROI": "45.83",
# "CurrencyCode": "USD",
# "CurrentOnDemandSpend": "4096.11",
# "EstimatedSavingsAmount": "1287.09",
# "EstimatedSavingsPercentage": "31.42",
# "HourlyCommitmentToPurchase": "3.85",
# "EstimatedMonthlySavingsAmount": "1287.09",
# "TotalRecommendationCount": "1"
# }

Read that as: promise $3.85 an hour, and the exact same workload costs $1,287 less every month. NO_UPFRONT folds the promise into the monthly bill. ALL_UPFRONT pays the whole term on day one for a slightly better rate. For work that genuinely does not mind being interrupted, such as batch renders or CI (continuous integration) build fleets, Spot still beats every commitment at up to 90% off, with the two-minute reclaim warning you met in the EC2 lesson.

You keep paying a Savings Plan even when nothing is using it
The promise is unconditional. That $3.85 leaves your account every hour for the full term whether the fleet shrinks, moves to Graviton (AWS's own cheaper processors), or gets deleted outright. There is one narrow escape hatch: plans committing $100/hour or less can be returned within 7 days of purchase, in the same calendar month you bought them. Outside that window, nothing cancels a plan mid-term. So buying the full recommendation right after a seasonal peak leaves you paying for commitment nobody uses for the rest of the year. Commit to your *floor*, not to your average. A common pattern is to buy 70–80% of what the tool recommends, then add small extra plans each quarter as the baseline proves it is really there.

Hunt the waste

Some of your bill buys nothing whatsoever. It is the cloud version of a storage unit you stopped visiting three years ago. The usual suspects: EBS (Elastic Block Store) volumes sitting in the available state, meaning detached from every instance and still billed; snapshots of servers that were deleted a year ago; load balancers nobody sends traffic to; log groups set to keep everything forever. Unattached volumes are the easiest thing to audit because the state leaves no room for argument. available means *nothing is using this*.

audit: volumes billing for nothing
aws ec2 describe-volumes \
--filters Name=status,Values=available \
--query 'Volumes[].{ID:VolumeId,GiB:Size,Type:VolumeType,Created:CreateTime}' \
--output table
# ---------------------------------------------------------------------
# | DescribeVolumes |
# +----------------------------+------+------------------------+------+
# | Created | GiB | ID | Type |
# +----------------------------+------+------------------------+------+
# | 2025-11-02T08:14:53+00:00 | 500 | vol-0c2d1e0f9a8b76543 | gp3 |
# | 2026-01-19T22:41:07+00:00 | 100 | vol-0a1b2c3d4e5f60789 | gp2 |
# +----------------------------+------+------------------------+------+

That 500 GiB (gibibyte, a unit of storage) gp3 volume charges roughly $40 a month to do absolutely nothing. Before you decide it is too small to be worth a ticket, multiply it by every region and every account you run. Then stop doing this by hand. Data Lifecycle Manager expires old EBS snapshots on a schedule for you, and S3 lifecycle rules slide cold objects down the storage tiers from the S3 lesson. When the exam tells you the access pattern is *unknown*, the answer it wants is S3 Intelligent-Tiering. Trusted Advisor bundles a lot of these checks (idle instances, unattached volumes, commitments you are barely using), though the full set of cost checks needs a Business, Enterprise On-Ramp, or Enterprise support plan.

Set tripwires with Budgets

Everything so far looks backwards at money already gone. AWS Budgets looks forward and shouts before a line gets crossed, more smoke alarm than fire report. The exam nuance is the notification type. An ACTUAL alert fires after the money is spent. A FORECASTED alert fires the moment this month's trajectory says you are heading for an overrun, which is early enough to actually do something. The catch is that AWS needs roughly five weeks of usage history before it can forecast anything at all. Budgets can also fire *budget actions* when a threshold breaks, such as attaching a deny-all IAM (Identity and Access Management) policy or stopping named EC2 and RDS (Relational Database Service) instances.

forecast alert at 80% of a $9k budget
aws budgets create-budget --account-id 111122223333 \
--budget '{"BudgetName":"monthly-total","BudgetLimit":{"Amount":"9000","Unit":"USD"},"TimeUnit":"MONTHLY","BudgetType":"COST"}' \
--notifications-with-subscribers '[{"Notification":{"NotificationType":"FORECASTED","ComparisonOperator":"GREATER_THAN","Threshold":80,"ThresholdType":"PERCENTAGE"},"Subscribers":[{"SubscriptionType":"EMAIL","Address":"[email protected]"}]}]'
# (no output on success — verify:)
aws budgets describe-budgets --account-id 111122223333 \
--query 'Budgets[].{Name:BudgetName,Limit:BudgetLimit.Amount,Period:TimeUnit}'
# [
# {
# "Name": "monthly-total",
# "Limit": "9000",
# "Period": "MONTHLY"
# }
# ]
The cost optimization loop
1See
Cost Explorer, tags, CUR
2Right-size
Compute Optimizer evidence
3Commit
Savings Plans on the floor
4Clean & guard
delete waste, Budgets tripwires
This is a loop, and it never finishes. Usage drifts every month, so the audit, the commitment level and the budget thresholds all come back around on a schedule.

Cost is the pillar where good architecture pays you back in actual dollars, and it is one of six views AWS expects you to hold at the same time. The next lesson pulls back to the Well-Architected Framework itself: the other five pillars, the design principles underneath them, and how the exam tests whether you can weigh cost against reliability, performance, security and operations when those four start pulling in different directions.

Right-sizing without measurements is guesswork in a spreadsheet. Look at CPU, memory (which needs the CloudWatch agent installed) and network traffic before you sign any Savings Plan. Delete unattached EBS volumes and stale snapshots as a routine. They are subscriptions nobody remembers starting.

Budgets and anomaly detection are tripwires, not a strategy. The strategy lives in the architecture: lifecycle tiers on storage, Graviton processors wherever your software runs on them, and non-production environments switched off on a schedule instead of humming through the weekend.

Write down the failure you actually fear, in your own words, rather than the one printed on the marketing slide. Then check that the architecture, the alarm and the runbook all name that same failure the same way.

Try this

Two commands that usually pay for themselves. Pull last month's spend grouped by service with ce get-cost-and-usage, then list every volume that is attached to nothing.

terminal
aws ce get-cost-and-usage --time-period Start=2026-06-01,End=2026-07-01 \
--granularity MONTHLY --metrics UnblendedCost \
--group-by Type=DIMENSION,Key=SERVICE \
--query 'ResultsByTime[0].Groups[:5].{Service:Keys[0],Cost:Metrics.UnblendedCost.Amount}' --output table
aws ec2 describe-volumes --filters Name=status,Values=available \
--query 'Volumes[].{Id:VolumeId,Size:Size,Type:VolumeType}' --output table
output
---------------------------------------------
| GetCostAndUsage |
+----------------------+--------------------+
| Service | Cost |
+----------------------+--------------------+
| Amazon Elastic Compute Cloud | 842.10 |
| Amazon Simple Storage Service | 210.44 |
+----------------------+--------------------+
vol-0abc | 100 | gp3
# available = unattached — candidate for delete after snapshot check

Takeaway

Work the order and it works for you: see the spend, then right-size, then commit, and delete waste the whole time. A budget alert only catches what the architecture failed to prevent.

Next: put a 15-minute cost review on the calendar every week. Start with the top three services and the unattached volumes, before anyone is allowed to argue about Savings Plans.

Quick check
01Cost Explorer recommends a 1-year Compute Savings Plan of $3.85/hour, worked out from the last 30 days, and those 30 days happened to include a seasonal traffic peak. What is the soundest way to commit?
Incorrect — The commitment is unconditional and cannot be cancelled mid-term, so buying the full recommendation right after a peak leaves you paying for empty commitment all year.
Correct — Commit to your floor rather than your average; smaller layered plans track the proven baseline without locking you into a peak that will not repeat.
Incorrect — Spot fits interruptible bulk work and can be reclaimed on a two-minute warning, so it is unfit for a fleet that has to stay up.
Incorrect — RIs are largely superseded by Savings Plans, and the narrow 7-day return window belongs to Savings Plans anyway, so it is no reason to prefer RIs.
02In AWS Budgets, what is the difference between a FORECASTED alert and an ACTUAL alert?
Incorrect — this has the two the wrong way round; the forecast is the forward-looking one, and ACTUAL fires only once the spend has happened.
Incorrect — alerts fire whenever the threshold condition is met during the period, not only at month end.
Incorrect — the forecast is the one that needs history: AWS wants roughly five weeks of usage before it can forecast at all.
Correct — the forecast arrives early enough for you to intervene, while ACTUAL arrives after the money is already gone.
03Compute Optimizer flags an m5.2xlarge that runs 24/7 and never went above 10 percent CPU over the last two weeks. The instance has to stay always-on. What is the MOST cost-effective action?
Correct — the loop is right-size on evidence first, then commit on the floor you have proven; downsizing plus a Savings Plan stacks both savings.
Incorrect — Spot fits interruptible bulk work and can be reclaimed on a two-minute warning, so it is unfit for an instance that has to stay up.
Incorrect — committing at the oversized rate locks the waste in for three years; right-size before you commit.
Incorrect — a budget alert reports spend; it does not shrink the cost of an over-provisioned instance.

Related