Work in order of leverage, not in order of interest
Most cost reviews start with the fun part — picking Savings Plans, arguing about Graviton, redesigning for serverless. That’s backwards. If you commit to a three-year Savings Plan on top of an account full of unattached EBS volumes and oversized RDS instances, you’ve just locked in your waste for three years.
The order that actually saves money, most leverage first:
- Eliminate waste — resources running that nobody needs.
- Rightsize — resources running at the correct size for observed load.
- Commit — buy discounts against a workload you’ve already cleaned up.
- Architect — redesign for cost only after the first three are done.
Each step changes the baseline the next step works against. Skip a step and you overpay at every step after it.
Step 1: Eliminate waste
Waste in AWS is boring and specific. It’s not a mysterious “cloud cost problem” — it’s a list of resource types that silently bill after their purpose has expired.
- Unattached EBS volumes. Every instance termination that doesn’t clean up its volumes leaves a billed, idle disk. Tag volumes with the instance and owner at creation time; without that, nobody can tell a live volume from an orphan six months later.
- Idle NAT gateways. A NAT gateway bills an hourly charge plus per-GB data processing, whether or not anything is routing through it. A NAT gateway in a subnet with no active workloads is pure waste — and it’s invisible unless someone checks VPC flow logs or the per-NAT-gateway cost line in Cost Explorer.
- Old snapshots. EBS and RDS snapshot retention left on “keep forever” from a migration two years ago. Snapshot storage is cheap per unit but compounds across thousands of daily snapshots with no lifecycle policy.
- Forgotten test/dev environments. The most common pattern: an environment spun up for a proof of concept, a demo, or a load test, and never torn down. These often run production-sized instances because nobody right-sized a throwaway environment.
- Elastic IPs not attached to a running instance, idle load balancers with no healthy targets, and orphaned RDS read replicas from a migration that never finished — all variations on the same theme.
None of this requires sophisticated tooling. AWS Cost Explorer, Trusted Advisor’s cost checks, and a quarterly “what’s running and why” review catch most of it. The hard part isn’t finding waste — it’s organizational: someone has to own the review, and someone has to have authority to terminate things that might belong to another team.
Step 2: Rightsize against real observation windows
Rightsizing without enough observation data just moves the waste rather than removing it. The common mistake is sizing off a single week that happened to be quiet, or off peak-only metrics that ignore how little headroom is actually used outside a few hours a month.
A defensible rightsizing exercise needs:
- At least one full business cycle of observation (month-end batch jobs, billing runs, seasonal traffic) — which is usually longer than the default window a tool will give you. This matters because AWS Compute Optimizer analyses CloudWatch metrics “from a period over the last 14 days” by default, and only extends to “up to 93 days” if you activate the enhanced infrastructure metrics recommendation preference (Metrics analysed by Compute Optimizer, AWS docs). A 14-day window straddling a quiet fortnight is exactly how a month-end batch job gets rightsized out of existence. Turn on the longer lookback before you trust the recommendations.
- P99 and P95, not average. Average CPU of 12% looks like a clear downsize candidate until you notice the P99 spikes to 85% during the nightly batch job that would fail on a smaller instance. Compute Optimizer itself works this way — it “uses the maximum utilization point within each five-minute time interval over the lookback period” for EC2, ASG, EBS and Lambda recommendations — so a recommendation derived from averages you computed yourself is a weaker input than the tool’s.
- Memory, not just CPU, for anything stateful — CPU-based rightsizing alone routinely misses memory-bound workloads, especially JVM and in-memory cache tiers.
- Burst-capable instance families (T-family) are a different rightsizing problem entirely — sized wrong, they either throttle under load or quietly burn CPU credits you’re not tracking.
Compute Optimizer currently analyses EC2 instances, EC2 Auto Scaling groups, EBS volumes, Lambda functions, ECS services on Fargate, commercial software licenses, and Aurora/RDS databases; check the current list before assuming a resource type is covered, since it has expanded over time. Note also that its analysis “can take up to 24 hours to complete” after opt-in, so it is not an answer you get the same afternoon. Treat its recommendations as a hypothesis to validate against your own peak-traffic knowledge, not a command to execute blindly.
Step 3: Commit — and understand what you’re actually buying
Once usage is real (waste gone, sizes correct), committing to discounted pricing is low-risk. Before that point, committing just means paying up front for mistakes.
| Option | What you commit to | Discount mechanism | Risk if usage changes |
|---|---|---|---|
| Savings Plans | A dollar/hour spend commitment for a one- or three-year term | Compute Savings Plans “provide lower prices on Amazon EC2 instance usage regardless of instance family, instance size, OS, tenancy, or AWS Region,” and the same rate applies to Fargate and Lambda usage (What are Savings Plans, AWS docs). EC2 Instance Savings Plans trade that flexibility for a deeper discount within a chosen family and Region | Lower risk — flexible application means a workload shift usually still gets covered |
| Reserved Instances | A specific instance type, region, and (for some RIs) AZ | Larger discount than equivalent Savings Plans in some cases, but inflexible | Higher risk — a discontinued or resized workload leaves the RI stranded; standard RIs can be sold on the RI Marketplace, convertible RIs can be exchanged |
| Spot Instances | Nothing — no commitment, discount in exchange for interruption risk | AWS reclaims capacity with an interruption notice “issued two minutes before Amazon EC2 stops or terminates your Spot Instance,” delivered as an EventBridge event and in instance metadata, on a best-effort basis (Spot Instance interruption notices, AWS docs). If you set hibernation as the interruption behaviour you get the notice but not two minutes of warning, because hibernation begins immediately | Only appropriate for fault-tolerant, stateless, or checkpointed workloads — batch processing, CI runners, stateless web tiers behind autoscaling |
The practical guidance: use Compute Savings Plans as the default commitment vehicle for steady-state baseline usage, because the flexibility to shift between instance families, Regions, and between EC2, Fargate and Lambda absorbs architectural change. Reserve Reserved Instances for genuinely static workloads you’re confident won’t move — a long-lived database tier, for instance. Use Spot aggressively for anything that tolerates interruption; it’s the largest discount available and the most underused.
Commitment terms (1-year vs 3-year) are a bet on how stable your architecture will be. A 3-year commitment on a platform mid-migration or mid-refactor is a bet you’re likely to lose. Check current discount percentages and plan types in AWS’s own pricing pages before committing — these change, and this document deliberately doesn’t quote figures that would be stale by the time you read them.
The cost nobody models: data transfer
Compute and storage show up clearly on the bill. Data transfer hides across several line items and routinely surprises teams who modeled everything else carefully.
- Cross-AZ transfer. Traffic that crosses an Availability Zone boundary inside a Region is chargeable, and the charge is per gigabyte rather than a flat fee — which is why a design decision made for availability shows up as a continuous, volume-proportional line item. VPC peering illustrates the shape: AWS states data transfer over a peering connection that stays within an Availability Zone is free “even if it’s between different accounts,” while charges apply once it crosses AZs or Regions (VPC peering pricing, AWS docs). Get the current rates and the exact directionality from the EC2 data transfer pricing page before modelling — this is a figure that moves. A chatty microservices architecture spread across AZs for availability can generate a meaningful, continuous cross-AZ bill that nobody budgeted because “it’s all in one region.”
- NAT gateway processing charges. NAT gateways bill both an hourly rate and a per-GB data processing charge. High-volume egress through a NAT gateway (container image pulls, API calls to external services, log shipping) can make the NAT gateway itself one of the larger line items in a VPC — architecturally, a NAT instance or VPC endpoints for AWS services (S3, DynamoDB, ECR) can remove large amounts of that traffic from the NAT gateway path entirely.
- Egress to the internet. Billed per GB and the rate structure has tiers; this is the cost most teams do model, but they often miss that CDN (CloudFront) in front of egress-heavy workloads changes both the cost and the rate structure because CloudFront’s own data transfer pricing differs from direct EC2/S3 egress.
The fix isn’t a tool — it’s putting a data transfer line in the architecture review for anything that crosses AZs or leaves the VPC, before it ships, not after the bill arrives.
Storage tiering
S3 Intelligent-Tiering moves objects between access tiers automatically based on observed access patterns, trading a small monitoring fee for not having to hand-manage lifecycle rules. For predictable access patterns (logs that are hot for 30 days then cold forever), explicit lifecycle rules to S3 Standard-IA, Glacier Instant Retrieval, Glacier Flexible Retrieval, or Glacier Deep Archive are usually cheaper than Intelligent-Tiering’s monitoring overhead, because you already know the pattern and don’t need AWS to infer it. The decision criterion: unknown or changing access pattern → Intelligent-Tiering; known, stable pattern → explicit lifecycle policy.
The organizational problem
Here’s the honest part: no dashboard fixes a cost problem that’s actually a behavior problem. Cost Explorer, Trusted Advisor, and third-party cost tools all answer “where did the money go” after the fact. None of them stop an engineer from launching an oversized instance, skipping a tag, or leaving a dev environment running over a long weekend.
Tagging discipline is the precondition for everything above. Without consistent cost-allocation tags (owner, environment, project) applied at creation time — ideally enforced via SCPs or tag policies rather than requested by email — nobody can attribute spend to a team, which means nobody can be asked to justify it, which means the review in Step 1 never happens with any teeth. A cost anomaly detection tool pointed at an untagged account tells you spend went up; it can’t tell you whose spend it was or whether it was legitimate.
The sustainable fix is behavioral: cost visibility in the PR review and the design review, not just the monthly finance meeting; budgets and alerts tied to a team’s own dashboard, not a central FinOps team’s; and tagging enforced by policy, not convention. Tools support that discipline. They don’t replace it.