open atlas
↑ Back to track
AWS, hands-on AWS · 07 · 01

Cost optimization: purchase options, egress, idle waste, and visibility

Cost is an architecture property. The biggest levers, in order: commit baseline compute to Savings Plans/Spot, kill idle waste, and stop paying for egress and NAT you did not need. Cost Explorer, Budgets, and activated tags give you the visibility to do it.

AWS Middle ◷ 18 min
Level
FoundationsJuniorMiddleSenior

The bill lands on the first of the month and it is forty percent over forecast. Nobody shipped a new feature. The team opens Cost Explorer and finds three quiet culprits: a fleet of EC2 instances running 24/7 entirely On-Demand because “we’ll buy Savings Plans later”; a single line item called “data transfer” that nobody can explain, traced back to a chatty service whose pods landed in a different Availability Zone from the database they hammer all day; and a NAT gateway processing terabytes for a workload that only ever talks to S3. None of it is a bug. Every dollar is for real compute and real bytes. The surprise is not that AWS charged — it’s that the architecture had been quietly choosing to pay full price, cross-AZ, through a metered gateway, for months, and nobody had attributed a cent of it because nothing was tagged.

By the end of this lesson you will be able to look at any AWS bill, name the three biggest quiet culprits on sight, and know which lever to pull first.

Lever 1: compute purchase options — match commitment to baseline

The same EC2 capacity has four prices, and the gap between the cheapest and the priciest is enormous. On-Demand is the default: maximum flexibility, no commitment, and the highest per-hour rate. You pay it for every hour an instance runs whether it is busy or idle. It is the right price for unpredictable or short-lived work — and the wrong price for a baseline that runs around the clock.

Savings Plans trade flexibility for a discount: you commit to a steady amount of compute spend (measured in dollars per hour) for a one- or three-year term, and in return you save up to ~72% versus On-Demand. There are two shapes worth knowing. A Compute Savings Plan is the flexible one — its discount (up to ~66% off) applies automatically across instance family, size, Region, OS, and across EC2, Fargate, and Lambda, so you can re-architect underneath it without losing the rate. An EC2 Instance Savings Plan is cheaper (up to ~72% off) but narrower: you commit to a specific instance family in a chosen Region, and the discount only follows size and OS changes within that family. Reserved Instances are the older mechanism that predates Savings Plans; they bundle a discount with optional capacity reservation and are largely superseded by Savings Plans for pure cost savings.

At the far end, Spot Instances sell AWS’s spare capacity at up to ~90% off On-Demand — with one catch: AWS can reclaim the instance at any time, giving only a two-minute interruption notice (delivered via instance metadata and an EventBridge event). Spot is a gift for interruptible, stateless, fault-tolerant work — batch jobs, CI runners, big-data processing, anything that can checkpoint and resume — and a trap for anything stateful that cannot survive being killed mid-flight.

The senior rule is to match commitment to the shape of demand: cover the steady 24/7 baseline with a Savings Plan or RI, absorb the unpredictable burst on top with On-Demand, and run interruptible batch on Spot. Pricing is region-dependent and changes over time — always confirm current numbers on the EC2 pricing page; the percentages here are AWS’s published up-to maximums, not a quote.

Lever 2: data transfer and NAT — the silent line items

Compute is the bill everyone watches; data transfer is the bill that surprises them. The rough mental model: ingress from the internet is generally free, egress is not, and traffic that crosses an Availability Zone boundary or a Region boundary is metered even when it never leaves AWS. A microservice architecture that gossips constantly between services in different AZs can rack up cross-AZ transfer charges that dwarf the compute doing the talking. The fix is architectural: keep chatty traffic in-AZ (co-locate the talkers), front internet egress with CloudFront so the origin serves bytes once and the CDN serves them cheaply thereafter, and reach AWS services like S3 and DynamoDB through VPC endpoints instead of routing that traffic out through a gateway.

That gateway is the second silent line item. A NAT gateway is billed two ways at once: a per-hour charge for every hour it is provisioned, plus a per-GB charge for every gigabyte it processes — both region-dependent (see NAT gateway pricing). The classic surprise is a private-subnet workload whose only “external” traffic is to S3: every byte is being metered through the NAT for no reason. AWS’s own guidance is to put resources in the same AZ as the NAT gateway to avoid stacking cross-AZ charges on top, and to use VPC interface or gateway endpoints for AWS-service traffic so it bypasses the NAT entirely.

# Find your top spend movers fast with Cost Explorer, grouped by usage type
aws ce get-cost-and-usage \
  --time-period Start=2026-05-01,End=2026-06-01 \
  --granularity MONTHLY \
  --metrics "UnblendedCost" \
  --group-by Type=DIMENSION,Key=USAGE_TYPE \
  --query 'ResultsByTime[0].Groups[?Metrics.UnblendedCost.Amount>`100`]'
# Look for *-DataTransfer-Out-Bytes, NatGateway-Bytes, and *-DataTransfer-Regional-Bytes

Lever 3: idle and waste — you pay for what you forgot

When you audit a bill for the first time, this category usually shocks people the most — because nothing is broken, it just wasn’t noticed. A large share of most bills is pure waste: capacity that is provisioned, billed, and doing nothing. The usual suspects are unattached EBS volumes left behind after instances were terminated, old snapshots nobody prunes, over-provisioned instances sized for a peak that never comes, idle RDS databases kept “just in case,” forgotten dev/test environments that run nights and weekends, and load balancers wired to nothing. Two tools attack this directly: Compute Optimizer analyzes utilization and recommends right-sizing (down a size, or to a cheaper family), and a simple scheduler that stops non-production instances outside working hours can cut a dev account’s compute bill by more than half — those environments are idle ~128 of the 168 hours in a week.

Lever 4: visibility — you can’t optimize what you can’t attribute

Every lever above is invisible without measurement, and the foundation is cost allocation tags. Tagging resources by team, env, and service lets you slice the bill by who owns what — but there is a sharp gotcha: a user-defined tag does not show up in cost reports until you activate it in the Billing and Cost Management console, and after you apply it, AWS notes a new tag key can take up to 24 hours to appear in the Billing console for activation, and once activated it only applies to costs going forward, not retroactively. Around the tags sit three tools: Cost Explorer to analyze and forecast spend, AWS Budgets to fire alerts (or even automated actions) when spend crosses a threshold, and Cost Anomaly Detection to catch a spike the day it starts instead of on the first of the month.

{
  "BudgetName": "monthly-prod-ceiling",
  "BudgetType": "COST",
  "TimeUnit": "MONTHLY",
  "BudgetLimit": { "Amount": "5000", "Unit": "USD" },
  "CostFilters": { "TagKeyValue": ["user:env$prod"] },
  "NotificationsWithSubscribers": [
    {
      "Notification": { "NotificationType": "ACTUAL", "ComparisonOperator": "GREATER_THAN", "Threshold": 80 },
      "Subscribers": [{ "SubscriptionType": "EMAIL", "Address": "platform-oncall@example.com" }]
    }
  ]
}
LeverTypical wasteFixTool
Compute purchase24/7 baseline on full On-DemandSavings Plan for baseline, Spot for batchCost Explorer SP recommendations
Data transferCross-AZ chatter, internet egressCo-locate in-AZ, CloudFront, VPC endpointsCost Explorer by usage type
NAT gatewayPer-hour + per-GB on AWS-bound trafficVPC endpoints for S3/DynamoDB, same-AZVPC flow logs, Cost Explorer
Idle wasteOrphan EBS, idle RDS, over-provisionedRight-size, schedule dev shutdownsCompute Optimizer
VisibilityUntagged, unattributable spendActivate cost allocation tags, set budgetsBudgets, Anomaly Detection
Why this works

Why is data transfer so easy to miss? Because no single resource shows it. Compute, storage, and databases each have a tidy line you can stare at; transfer is an emergent property of how the pieces talk. A cross-AZ charge has no owning resource — it is the byproduct of two services landing in different AZs, a decision made by a scheduler, not a human. That is exactly why tagging and Cost Explorer’s usage-type grouping matter: the only way to see transfer cost is to slice the bill by the flow, because nothing in the console will surface it as “this service is expensive to talk to.”

Pick the best fit

A production API runs a steady fleet of ~20 instances 24/7 at high utilization, with occasional traffic spikes, plus a nightly batch pipeline that re-encodes media and can safely restart from a checkpoint. You want the cheapest mix without risking the API's availability. Pick the purchase strategy.

Quiz

You commit to a Compute Savings Plan, then later migrate a service from EC2 to Fargate and shift it to another Region. What happens to your discount?

Quiz

Your bill shows a large, growing 'data transfer' charge, but every instance looks correctly sized. What is the most likely architectural cause?

Recall before you leave
  1. 01
    List the compute purchase options from priciest to cheapest, and the rule for matching them to a workload.
  2. 02
    Where does money silently leak beyond compute, and which tools give you the visibility to catch it?
Recap

Cost is an architecture property, and the levers pay off in order of leverage. Now when you open Cost Explorer and see a bill that surprises you, run through the four levers in order before assuming it is a pricing error — nine times out of ten, the charge is real and the fix is architectural. First, match compute purchase options to demand shape: On-Demand is the flexible full-price default, Savings Plans take up to ~72% off in exchange for a 1- or 3-year dollars-per-hour commitment (the Compute Savings Plan staying flexible across instance family, Region, and EC2/Fargate/Lambda, the EC2 Instance plan trading flexibility for a slightly deeper discount locked to one family and Region), and Spot sells spare capacity at up to ~90% off but can be reclaimed with only a two-minute notice — so commit the steady 24/7 baseline, pay On-Demand for the burst on top, and run interruptible, stateless batch on Spot. Second, stop paying for data movement you didn’t need: cross-AZ chatter and internet egress are metered, and a NAT gateway charges per hour and per GB, so co-locate chatty services in one AZ, front egress with CloudFront, and route AWS-service traffic through VPC endpoints. Third, kill idle waste — orphan EBS, idle RDS, over-provisioned instances, forgotten dev environments — with Compute Optimizer right-sizing and scheduled shutdowns. Fourth, make all of it visible: activate cost allocation tags so spend is attributable by team, env, and service, then run Cost Explorer, Budgets, and Anomaly Detection. All prices here are AWS’s published up-to maximums and are region-dependent and illustrative — confirm current numbers on the AWS pricing pages before you quote them.

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 6 done
Connected lessons

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

Apply this

Put this lesson to work on a real build.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources5
expand
  1. 01
  2. 02
  3. 03
  4. 04
  5. 05

Trademarks belong to their respective owners. Editorial reference only.