AWS Cost

AWS Cost Optimization & FinOps: Right-Sizing, Savings Plans & Waste Elimination

Quick take: AWS cost optimization is not one big cut — it is four levers pulled in a deliberate order, run as a continuous loop. Right-size FIRST (Compute Optimizer + Graviton) so you commit to the real baseline, not a guessed one. Commit to that baseline with Savings Plans (Compute SP up to ~66%, EC2 Instance SP up to ~72%) and Reserved Instances, and push interruptible work to Spot (up to ~90% off). Eliminate waste relentlessly — idle instances, unattached EBS, old snapshots, unassociated EIPs, gp2→gp3, S3 lifecycle, log retention. And architect for pay-per-use with serverless, auto-scaling, and caching. Wrap all four in the FinOps Inform → Optimize → Operate loop, on a foundation of tagging and the CUR as the source of truth. Do it out of order — commit before right-sizing — and you lock in waste for three years.

A platform team gets a mandate: “cut the AWS bill 30% this quarter.” The instinct is to buy a big three-year Reserved Instance commitment on day one, because the discount looks huge. Six weeks later they finish a right-sizing exercise, discover half the fleet was two sizes too big, downsize it — and now they are paying for reserved capacity they no longer use, utilization has cratered to 55%, and the “savings” evaporated into stranded commitment. The lesson every FinOps practitioner learns the hard way: the levers have an order. Right-size before you commit. Eliminate waste before you optimize what remains. Measure before you touch anything.

This article is the senior engineer’s playbook for doing it correctly. It is organized the way you should actually run cost optimization — as four levers inside a repeatable loop, not as a one-time cleanup. You will learn to pull right-sizing signals from AWS Compute Optimizer (EC2, Auto Scaling groups, EBS, Lambda, ECS on Fargate) backed by the CloudWatch metrics that matter, when to downsize versus modernize a family, and how a Graviton (ARM) migration buys ~20% better price-performance. You will learn the full commitment ladderCompute vs EC2 Instance vs SageMaker Savings Plans, Standard vs Convertible Reserved Instances, regional vs zonal, term and payment options, and Spot — and how to reason about coverage versus utilization so you never over- or under-commit. You will learn to systematically eliminate waste: idle and stopped resources, unattached EBS volumes and orphaned snapshots, unassociated Elastic IPs, gp2→gp3, S3 lifecycle plus Intelligent-Tiering plus incomplete-multipart cleanup, CloudWatch Logs retention, over-provisioned RDS/OpenSearch/Kinesis/DynamoDB, and NAT/data-transfer reduction. Finally you will build the FinOps operating modelInform → Optimize → Operate, showback/chargeback, tagging and cost allocation as the foundation, unit economics (cost per customer, per request), budgets with anomaly detection, and the CUR as the single source of truth.

By the end you will not just know the tools; you will know the sequence, the trade-offs, and the traps — right-sizing an instance into an out-of-memory crash, stranding a Savings Plan, buying an RI in the wrong region, running the wrong workload on Spot, flipping gp2→gp3 without checking the IOPS. Every lever comes with the aws CLI, Terraform where it genuinely applies (and an honest note where it does not), real rates, and a teardown.

What problem this solves

AWS is pay-as-you-go with no default ceiling, which means cost is an emergent property of thousands of individual decisions — an instance size here, a forgotten volume there, a data-transfer path nobody drew on the diagram. Left alone, a healthy AWS estate accretes 20–40% pure waste: over-provisioned compute sized for a load test that never recurs, storage nobody deletes, commitments that expired or never fit, and egress routed the expensive way. None of it shows up as an outage. It shows up as a bill that grows faster than the business.

The pain has three distinct shapes, and each lever targets one:

Underneath all three is a governance problem: you cannot optimize what you cannot attribute. Without tags and a source-of-truth dataset (the CUR), “cut 30%” is a guess, savings are unverifiable, and no team owns its own spend. That is why FinOps is an operating model, not a project — the Inform → Optimize → Operate loop institutionalizes measurement, action, and accountability so the 30% you cut this quarter does not silently grow back next quarter. Who hits this? Everyone past the free-tier stage: startups burning runway, enterprises with a CFO asking why cloud is the second-biggest line item, and platform teams told to fund new work “from savings.”

Learning objectives

By the end of this article you can:

Prerequisites & where this fits

You need an AWS account with billing/Cost Explorer access (management account or a principal granted ce, compute-optimizer, budgets, cur, and read on ec2), the AWS CLI v2 configured, and ideally Cost Explorer already enabled and a few weeks of history so recommendations have a baseline. Terraform is used where it genuinely codifies a lever (budgets, anomaly monitors, gp3 volumes, Compute Optimizer enrollment) — and this article is explicit about where it does not (you cannot idempotently “buy” a Savings Plan or RI in Terraform; that is a deliberate purchase).

Where this fits: this is the capstone of the cost/FinOps track. It assumes you already have the guardrails from AWS Cost Control 101: Cost Explorer, Budgets, Alerts & Free-Tier Guardrails (you cannot optimize without visibility and trip-wires), and it stands on the attribution foundation from AWS Tagging Strategy & Cost-Allocation Enforcement (without tags, none of this is measurable per team). It draws heavily on compute knowledge from Choosing EC2 Instance Types & Families and pairs with AWS EC2 Spot Instances: Savings & Interruptions for the interruptible lever. When a bill spikes, you triage it with AWS Cost-Spike & Anomaly-Detection Troubleshooting, and this whole discipline is the Cost Optimization pillar of the companion AWS Well-Architected Framework Review (wave sibling).

Core concepts

Cost optimization has a small but precise vocabulary. Get these exact, because the tools, the exam, and the invoice all use them literally.

Term What it means Why it matters
FinOps Cultural + operational practice bringing financial accountability to variable cloud spend The framing: engineers, finance, product share ownership
Inform → Optimize → Operate The three iterative FinOps phases Sequence: see it, act on it, run it as a loop
Right-sizing Matching resource size/type to actual utilization Usually the biggest single win; compounds
Commitment A 1/3-yr promise (Savings Plan / RI) traded for a discount Cuts the steady baseline 40–72%
Coverage % of eligible usage covered by a commitment High = more savings, more risk
Utilization % of a commitment you actually consume Low = you over-committed (stranded)
Savings Plan (SP) A $/hour spend commitment for a discount Flexible commitment vehicle
Reserved Instance (RI) A capacity/billing reservation for a discount Older, less flexible than SP
Spot Spare capacity at up to ~90% off, interruptible For fault-tolerant / stateless work
Graviton AWS ARM-based CPUs (Graviton2/3/4) ~20% better price-performance
Waste Paid-for resources delivering no value Idle, unattached, orphaned, over-tiered
Showback / Chargeback Reporting (showback) vs billing back (chargeback) cost to teams Drives accountability
Unit economics Cost per business unit (customer, request, GB) The only metric that scales with the business
CUR Cost and Usage Report — hourly line-item export to S3 The source of truth
Amortized cost Upfront commitment fees spread across the term True run-rate with commitments

The four levers, in order

The single most important idea in this article is that the levers are sequenced, because each one changes the input to the next.

# Lever Typical saving Effort Risk Why this order
1 Right-size (Compute Optimizer, Graviton) 20–50% on affected resources Medium Under-provisioning / OOM Do first: shrinks what you then commit to
2 Commit (Savings Plans, RIs, Spot) 40–72% (SP/RI), up to 90% (Spot) Low–Medium Stranding, wrong scope After right-sizing, so the baseline is real
3 Eliminate waste (idle, unattached, lifecycle) 5–20% of total bill Low Deleting something in use Continuous; cheap; do always
4 Architect (serverless, auto-scale, cache) Structural, workload-specific High Re-engineering risk Longest horizon; biggest structural wins

If you invert 1 and 2 — commit before right-sizing — you strand the commitment the moment you shrink the fleet. If you skip lever 3, waste regenerates faster than you cut it. Lever 4 is the slow, structural work that pays off over quarters.

The FinOps loop

Phase Goal Key activities Tools Output
Inform Visibility & allocation Tag, activate cost-allocation tags, build CUR, showback Cost Explorer, CUR, tags, Cost Categories “Who spends what, on what”
Optimize Reduce & commit Right-size, buy SP/RI, kill waste, re-architect Compute Optimizer, ce recs, Trusted Advisor Realized savings
Operate Govern continuously Budgets, anomaly alerts, unit-cost KPIs, reviews Budgets, Cost Anomaly Detection, dashboards Sustained, no regrowth

The commitment ladder

Think of purchasing as a ladder from cheapest-but-riskiest to safest-but-dearest. You cover each layer of demand with the right rung.

Rung Discount vs on-demand Commitment Interruptible? Best for
Spot Up to ~90% None Yes (2-min notice) Batch, CI, stateless, fault-tolerant
EC2 Instance SP Up to ~72% 1/3-yr $/hr, one family+region No Stable, known-family baseline
Standard RI Up to ~72% 1/3-yr, specific config No Stable, unchanging config; zonal capacity
Compute SP Up to ~66% 1/3-yr $/hr, any family/region No Baseline you want flexibility on
Convertible RI Up to ~66% 1/3-yr, exchangeable No Baseline that may change family/OS
On-Demand 0% (baseline) None No Spiky, unpredictable, short-lived peak

The design pattern: cover the flat 24×7 baseline with Savings Plans, put interruptible work on Spot, and leave the spiky top-of-the-curve on On-Demand. You almost never want 100% coverage — the variable peak belongs on-demand or Spot.

Right-sizing: Compute Optimizer, CloudWatch signals, and Graviton

Right-sizing is lever one because it changes every downstream number. It has three moves: downsize (same family, smaller), modernize (newer generation, same or better perf for less), and re-platform to Graviton (ARM for price-performance). AWS’s engine for all of this is Compute Optimizer.

AWS Compute Optimizer

Compute Optimizer is a free service that analyzes ~14 days of CloudWatch metrics (and configuration) and classifies each resource, then recommends a specific target with a projected price and performance risk. Enroll the account (or the whole org from the management account) once.

Resource type Signals it reads Recommendation output Notes
EC2 instances CPU, network, EBS I/O, (memory if agent) Up to 3 optimal instance options + risk Core use case
Auto Scaling groups Aggregate ASG utilization Recommended instance type for the ASG Right-sizes the launch template
EBS volumes IOPS, throughput utilization gp3/size/IOPS recommendation Finds gp2→gp3 + over-provisioned IOPS
Lambda functions Duration, memory, cost Recommended memory setting Memory drives both speed and cost
ECS services on Fargate Task CPU/memory utilization Recommended task size Fargate right-sizing
Commercial software licenses License metrics License edition recommendations E.g. SQL Server edition
RDS (where supported) DB instance + storage utilization Instance class / storage recs Newer coverage; verify in your region
Finding class Meaning Your action
Under-provisioned Resource is too small; risk to performance Upsize (this can raise cost — reliability first)
Over-provisioned Resource is bigger than the load needs Downsize — the savings finding
Optimized Right-sized already Leave it
None / not enough data < 30 hrs of metrics, or unsupported Wait for data; install agent for memory

The paid add-on, enhanced infrastructure metrics, extends the lookback to 3 months so recommendations reflect monthly cycles (e.g. month-end batch) — worth it for spiky workloads.

# Enroll this account (or --include-member-accounts from the org management account)
aws compute-optimizer update-enrollment-status --status Active

# Confirm enrollment + how many resources are analysed
aws compute-optimizer get-enrollment-status

# Pull over-provisioned EC2 recommendations with the projected savings
aws compute-optimizer get-ec2-instance-recommendations \
  --filters name=Finding,values=Overprovisioned \
  --query 'instanceRecommendations[].{Id:instanceArn,Current:currentInstanceType,
           Rec:recommendationOptions[0].instanceType,
           Risk:recommendationOptions[0].performanceRisk}' --output table

# EBS volume recommendations (finds gp2→gp3 and over-provisioned IOPS)
aws compute-optimizer get-ebs-volume-recommendations \
  --query 'volumeRecommendations[].{Vol:volumeArn,
           Cur:currentConfiguration.volumeType,
           Rec:volumeRecommendationOptions[0].configuration.volumeType}' --output table

# Lambda memory right-sizing
aws compute-optimizer get-lambda-function-recommendations \
  --query 'lambdaFunctionRecommendations[].{Fn:functionArn,
           CurMem:currentMemorySize,
           RecMem:memorySizeRecommendationOptions[0].memorySize}' --output table

There is no first-class Terraform resource to “buy” a recommendation — right-sizing is an apply-with-judgment action. You can codify enrollment via the AWS API/CLI in a null-resource or an Organizations-level opt-in, and you codify the result (the new instance type in your launch template / aws_instance). Treat Compute Optimizer output as a work queue, not an auto-apply.

The CloudWatch signals that matter (and the memory trap)

Compute Optimizer is only as good as the metrics it sees, and the default EC2 metric set has a gaping hole: memory is not a default CloudWatch metric. The hypervisor cannot see guest RAM. If you right-size on CPU alone, you will happily shrink a memory-bound instance into an out-of-memory crash.

Signal Source Default? Right-size read Caveat
CPUUtilization Hypervisor Yes Sustained low → downsize candidate Burstable (T-family) hides real need in credits
Memory CloudWatch agent only No The missing dimension Right-size without it → OOM
NetworkIn/Out Hypervisor Yes High → don’t shrink network-bound Some sizes cap bandwidth
EBSReadOps/WriteOps Hypervisor Yes High IOPS → mind EBS-optimized limits Shrinking can cut EBS bandwidth
DiskReadBytes (instance store) Hypervisor Yes Local disk pressure Only for instance-store types
CPUCreditBalance Hypervisor (T-family) Yes Draining → under-provisioned burstable Standard vs unlimited mode differs
StatusCheckFailed Hypervisor Yes Health, not sizing
Custom app metrics Your app / agent No p99 latency, queue depth, RPS The real business signal

The rule: before you downsize, confirm memory headroom via the CloudWatch agent (or an APM), and keep 20–30% headroom on the constraining dimension so a normal spike does not push you into throttling or OOM. Right-sizing is not “pick the smallest that fits the average”; it is “pick the smallest that survives the p99 with headroom.”

Downsize vs modernize the family

Move Example Typical gain Watch-out
Downsize within family m5.2xlarge → m5.xlarge ~50% (half the size) Memory/IOPS headroom, connection limits
Modernize generation m4.xlarge → m6i.xlarge ~10–15% better price-perf Requires ENA, NVMe drivers, newer AMI
Right-family General m → compute c or memory r Fewer, cheaper right-shaped instances Re-benchmark; wrong family wastes the other axis
Burstable for spiky low-avg m6i.large → t3.large Cheaper if avg CPU is low Credits exhaust under sustained load
Re-platform to Graviton m6i.xlarge → m7g.xlarge ~20% price-perf, sometimes more ARM recompile / multi-arch image

Modernizing is the most overlooked lever: newer generations (m4→m5→m6i→m7i) deliver more performance per dollar almost for free, but require a modern AMI with ENA and NVMe support — an old pinned AMI silently keeps you on expensive hardware.

Graviton: the price-performance lever

Graviton is AWS’s own ARM64 silicon (Graviton2 in *6g, Graviton3 in *7g, Graviton4 in *8g/r8g). For most scale-out, throughput-oriented workloads it delivers roughly 20% better price-performance than the equivalent x86 generation, and up to ~40% on some (web tiers, containerized microservices, in-memory caches, managed databases).

Aspect x86 (Intel/AMD) Graviton (ARM64) Note
Instance suffix m6i, c6a, r6i m7g, c7g, r7g g = Graviton
Price-performance Baseline ~20% better (workload-dependent) Measured, not list price alone
Binary compatibility x86_64 arm64 — must recompile The main cost of the move
Managed services Supported RDS, ElastiCache, OpenSearch, Lambda, Fargate Often a one-line switch
Container images linux/amd64 linux/arm64 — build multi-arch docker buildx, manifest lists
Interpreted / JIT langs Runs Runs (Java, Python, Node, Go) Usually zero code change
Native deps / drivers x86 binaries Need arm64 builds Check every native dependency

The migration playbook: start where the switch is nearly free — Lambda (set Architectures=["arm64"]), RDS/ElastiCache/OpenSearch (choose a Graviton instance class), and Fargate (cpuArchitecture: ARM64) — then move interpreted-language EC2/EKS workloads (build multi-arch images), and leave anything with x86-only native dependencies for last. Validate performance per workload; the price-perf gain assumes your app scales cleanly on ARM.

Purchasing & commitments: Savings Plans, RIs, and Spot

Once the fleet is right-sized, you cover the steady part of it with a commitment. Buying before right-sizing is the classic stranding mistake; buying the wrong shape is the classic flexibility mistake.

Savings Plans — three flavours

A Savings Plan is a commitment to spend a certain $/hour for 1 or 3 years, in exchange for a discount on matching usage. It is not tied to a specific instance; it is a spend commitment that AWS applies automatically to your highest-discount eligible usage first.

Savings Plan type Applies to Flexibility Max discount Choose when
Compute SP EC2 (any family/size/region/OS/tenancy), Fargate, Lambda Highest — region/family/service agnostic ~66% You want savings and freedom to change shape
EC2 Instance SP One instance family in one region (any size/OS/tenancy) Medium — locked to family+region ~72% A large, stable, known-family baseline
SageMaker SP SageMaker ML instance usage SageMaker-scoped ~64% Steady ML training/inference

The trade is explicit: EC2 Instance SP gives ~6 points more discount than Compute SP in exchange for locking the family and region. If you are confident m7g in ap-south-1 is your baseline for three years, take the extra discount. If you might shift to Graviton, Fargate, or another region, the Compute SP’s flexibility is worth the six points — it will keep applying its discount as you re-architect.

Reserved Instances — standard vs convertible, regional vs zonal

Reserved Instances predate Savings Plans and are being superseded by them for EC2, but they remain essential for services SPs do not cover (RDS, ElastiCache, OpenSearch, Redshift, DynamoDB reserved capacity) and where you need a zonal capacity reservation.

RI dimension Standard RI Convertible RI
Max discount Up to ~72% Up to ~66%
Change family/OS/tenancy No (modify size within family, Linux) Yes — exchange for a different config
Sell in RI Marketplace Yes No
Best for Config you are certain of for the term Config that may evolve
Scope Standard behaviour
Regional RI Billing discount only, no capacity reservation; AZ + size flexibility (normalization factors) across the region
Zonal RI Capacity reservation in a specific AZ; no size flexibility — guarantees you can launch

Size flexibility on regional Linux RIs works through normalization factors — a reservation floats across sizes in the family by “footprint”:

Size Normalization factor
nano 0.25
micro 0.5
small 1
medium 2
large 4
xlarge 8
2xlarge 16
4xlarge 32

So one m6i.4xlarge regional RI (factor 32) can instead cover eight m6i.large (4 each) — the discount applies by footprint, not by literal size. This is why a regional Convertible RI is the most flexible RI, and why for pure EC2 most teams now prefer a Compute Savings Plan (even more flexible, covers Fargate/Lambda too).

Term and payment options

Both SPs and RIs are bought on a 1-year or 3-year term with one of three payment options, and the discount ladders with your commitment:

Term × Payment Relative discount Cash impact Choose when
1yr No Upfront Lowest None upfront, monthly Uncertain baseline, preserve cash
1yr Partial Upfront Low–mid Half upfront Balanced
1yr All Upfront Mid Full upfront Have cash, want more off a 1-yr bet
3yr No Upfront Mid–high None upfront, monthly Confident baseline, keep cash
3yr Partial Upfront High Half upfront Confident + some cash
3yr All Upfront Highest Full 3-yr upfront Max discount, very stable baseline, cash-rich

The pragmatic default for most teams: 1-year, No Upfront, Compute Savings Plan — it captures the bulk of the discount, preserves cash and flexibility, and is easy to re-ladder each year as the baseline grows. Step up to 3-year All Upfront only for the rock-stable floor of your usage.

Spot for interruptible workloads

Spot Instances are spare EC2 capacity at up to ~90% off on-demand, reclaimed with a 2-minute warning when AWS needs the capacity back. Spot is not a discount you “buy” — it is a different fulfillment model for work that can tolerate interruption.

Workload Spot fit? Why
CI/CD build agents Excellent Retryable, stateless, short
Batch / data processing Excellent Checkpoint + resume; EMR/Batch handle it
Containerized stateless web Good Behind an ELB with capacity-rebalance + on-demand base
ML training (checkpointed) Good Resume from checkpoint on reclaim
Fault-tolerant queue workers Good SQS re-delivers on interruption
Stateful single-node DB No Interruption = data/availability loss
Licensed/quota-limited app Caution Re-launch churn may break licensing
Long non-checkpointed job No Loses hours of work on reclaim

The mature pattern is a mixed-instances Auto Scaling group / EC2 Fleet: an on-demand (or Savings-Plan-covered) base capacity for the floor, plus a Spot portion across many instance types and AZs for the variable top, with capacity-rebalancing to drain gracefully. Full mechanics, interruption handling, and interruption-rate data are in AWS EC2 Spot Instances: Savings & Interruptions.

Coverage vs utilization — the two numbers that govern commitments

These two metrics are the whole game, and confusing them is how teams both over- and under-commit.

Metric Question it answers Formula (intuition) Target If wrong
Utilization “Am I using the commitment I bought?” used commitment ÷ committed ≥ 95% Low → you over-committed (stranded $)
Coverage “How much of my usage is on a commitment?” eligible usage on a commitment ÷ total eligible ~70–85% Low → leaving on-demand savings on the table; too high → risk

The design rule that reconciles them: commit to the baseline, not the peak. Set coverage so your commitment sits under the 24×7 floor of your usage curve — then utilization naturally stays near 100% (you always use at least the floor), and the variable usage above the floor rides on-demand or Spot. If you push coverage toward 100%, the first time you right-size or a workload ends, utilization collapses and you pay for nothing.

# What does AWS recommend you commit? (60-day lookback, 1yr, no-upfront Compute SP)
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 SIXTY_DAYS \
  --query 'SavingsPlansPurchaseRecommendation.SavingsPlansPurchaseRecommendationSummary.{
           Hourly:HourlyCommitmentToPurchase,
           MonthlySavings:EstimatedMonthlySavingsAmount,
           ROI:EstimatedSavingsPercentage}' --output table

# Are the commitments I already own being used and covering enough?
aws ce get-savings-plans-utilization \
  --time-period Start=2026-06-01,End=2026-07-01 \
  --query 'Total.Utilization.UtilizationPercentage'

aws ce get-savings-plans-coverage \
  --time-period Start=2026-06-01,End=2026-07-01 \
  --query 'Total.Coverage.CoveragePercentage'

# EC2 RI angle: recommendation, coverage, utilization
aws ce get-reservation-purchase-recommendation --service "Amazon Elastic Compute Cloud - Compute"
aws ce get-reservation-coverage    --time-period Start=2026-06-01,End=2026-07-01
aws ce get-reservation-utilization --time-period Start=2026-06-01,End=2026-07-01

Terraform reality check: there is no aws_savingsplan or “buy RI” resource — a commitment is a financial purchase you make deliberately in the console or via a purchase CLI after reviewing the recommendation, precisely so it is never applied accidentally by terraform apply. What you do codify in Terraform is the governance around it: coverage/utilization budgets and anomaly monitors that alert when a commitment drifts.

# Alert if Savings Plans utilization drops below 95% (you over-committed / stranded)
resource "aws_budgets_budget" "sp_utilization" {
  name         = "sp-utilization-floor"
  budget_type  = "SAVINGS_PLANS_UTILIZATION"
  limit_amount = "95"
  limit_unit   = "PERCENTAGE"
  time_unit    = "MONTHLY"

  notification {
    comparison_operator        = "LESS_THAN"
    threshold                  = 95
    threshold_type             = "PERCENTAGE"
    notification_type          = "ACTUAL"
    subscriber_email_addresses = ["finops@example.com"]
  }
}

# Alert if coverage falls below your target (you're leaving discount on the table)
resource "aws_budgets_budget" "sp_coverage" {
  name         = "sp-coverage-target"
  budget_type  = "SAVINGS_PLANS_COVERAGE"
  limit_amount = "75"
  limit_unit   = "PERCENTAGE"
  time_unit    = "MONTHLY"

  notification {
    comparison_operator        = "LESS_THAN"
    threshold                  = 75
    threshold_type             = "PERCENTAGE"
    notification_type          = "ACTUAL"
    subscriber_email_addresses = ["finops@example.com"]
  }
}

Eliminating waste: the continuous sweep

Waste is the easiest saving to realize — no re-architecture, no commitment, no risk beyond deleting something in use. It is also the saving that regenerates, so this is a recurring sweep, ideally automated. Here is the catalog, with how each bills, how to find it, and the fix.

# Waste item How it bills Find it (CLI / Cost Explorer) Fix Rough rate
1 Idle running EC2 Per-second, 24×7 Compute Optimizer “not enough / low CPU”; CW < 5% CPU Stop/terminate or schedule Instance rate
2 Stopped EC2 (its EBS) EBS still bills while stopped describe-instances state=stopped + attached vols Snapshot + delete, or terminate ~$0.08/GB-mo
3 Unattached EBS volumes Full volume rate, detached describe-volumes status=available Snapshot then delete ~$0.08/GB-mo (gp3)
4 Orphaned snapshots Per-GB, forever, incremental describe-snapshots --owner-ids self Lifecycle / DLM retention policy ~$0.05/GB-mo
5 Unassociated Elastic IPs Idle EIP hourly describe-addresses AssociationId==null Release them ~$0.005/hr
6 Public IPv4 (all) Every public IPv4 hourly Usage type PublicIPv4:InUseAddress Reduce public IPs; use IPv6/private ~$0.005/hr
7 gp2 volumes 20% pricier than gp3 describe-volumes volume-type=gp2 Modify to gp3 (live) gp2 $0.10 vs gp3 $0.08/GB-mo
8 Idle load balancers Per-hour + LCU/NLCU Service ELB, CW RequestCount≈0 Delete unused ALB/NLB ~$0.0225/hr + units
9 Verbose CloudWatch Logs Ingest + storage Service CloudWatch, IncomingBytes Cut log level; set retention; IA class ~$0.50/GB ingest
10 Infinite log retention Storage forever Log groups with retention “Never expire” Set 7/30/90-day retention ~$0.03/GB-mo
11 Incomplete multipart uploads Hidden S3 storage S3 Storage Lens / lifecycle metrics Lifecycle AbortIncompleteMultipartUpload S3 storage rate
12 Un-tiered S3 (Standard) Standard rate for cold data Storage Lens age/access analysis Lifecycle → IA/Glacier or Int-Tiering $0.023 → $0.004/GB-mo
13 Old S3 object versions Every version billed Versioned buckets, noncurrent versions Lifecycle expire noncurrent S3 storage rate
14 Over-provisioned RDS Instance + Multi-AZ + storage CE by instance type; low CPU/conn Downsize, Single-AZ (non-prod), gp3 Instance + storage
15 Provisioned-but-idle DynamoDB/Kinesis Provisioned capacity/shards CE service view; low consumed On-demand or auto-scaling Varies
16 Extra NAT gateways Per-AZ hourly + per-GB Usage type NatGateway-* Consolidate; VPC endpoints $0.045/hr + $0.045/GB

gp2 → gp3: free money, with one check

Migrating gp2 to gp3 is one of the highest-ROI moves in AWS: gp3 is about 20% cheaper per GB and decouples IOPS/throughput from size. It is a live modification — no downtime, no detach. The one check: gp2 gave you 3 IOPS/GB (and burst), so a large gp2 volume may have had more than gp3’s free baseline. gp3 includes 3,000 IOPS + 125 MB/s free; if your gp2 volume was ≥1,000 GB (≥3,000 IOPS baseline) or you rely on burst, you must provision matching IOPS/throughput on gp3 or you will regress performance.

Attribute gp2 gp3 Note
Price / GB-mo ~$0.10 ~$0.08 ~20% cheaper
Baseline IOPS 3 IOPS/GB (100–16,000) 3,000 free, up to 16,000 gp3 flat baseline
Burst IOPS To 3,000 (< 1,000 GB) N/A (baseline already 3,000) gp2 burst gotcha
Baseline throughput Scales with IOPS 125 MB/s free, up to 1,000 gp3 decoupled
Extra IOPS cost Included in $/GB ~$0.005/provisioned IOPS-mo > 3,000 Only if you need > 3,000
Extra throughput cost Included ~$0.04/MB/s-mo > 125 Only if you need > 125
Max size 16 TiB 16 TiB Same
# Find gp2 volumes and their size/IOPS so you can match on gp3
aws ec2 describe-volumes --filters Name=volume-type,Values=gp2 \
  --query 'Volumes[].{Id:VolumeId,GiB:Size,IOPS:Iops,AZ:AvailabilityZone}' --output table

# Live-migrate a small gp2 (< 1000 GiB, default 3000 IOPS is enough)
aws ec2 modify-volume --volume-id vol-0abc123 --volume-type gp3

# Large gp2 that had > 3000 baseline IOPS: match IOPS + throughput explicitly
aws ec2 modify-volume --volume-id vol-0def456 --volume-type gp3 \
  --iops 6000 --throughput 250
# New volumes: default to gp3 with the throughput/iops you actually need
resource "aws_ebs_volume" "data" {
  availability_zone = "ap-south-1a"
  size              = 500
  type              = "gp3"
  iops              = 3000   # free baseline; raise only if measured need
  throughput        = 125    # free baseline
  tags = { Name = "app-data", Environment = "prod", CostCenter = "platform" }
}

S3 lifecycle, Intelligent-Tiering, and incomplete uploads

S3 is a common waste sink because data lands in Standard and nobody moves it. Three levers:

Storage class Use case Min duration Retrieval Rough $/GB-mo Retrieval fee
S3 Standard Hot, frequent None Instant ~$0.023 None
Intelligent-Tiering Unknown/changing access None Instant (auto-tiers) ~$0.023 → $0.004 None (monitoring fee)
Standard-IA Infrequent, ms access 30 days Instant ~$0.0125 Per-GB
One Zone-IA Re-creatable infrequent 30 days Instant ~$0.01 Per-GB
Glacier Instant Retrieval Archive, instant 90 days ms ~$0.004 Per-GB
Glacier Flexible Retrieval Archive, mins–hours 90 days Minutes–hours ~$0.0036 Per-GB
Glacier Deep Archive Cold, rarely 180 days ~12 hours ~$0.00099 Per-GB
resource "aws_s3_bucket_lifecycle_configuration" "data" {
  bucket = aws_s3_bucket.data.id

  rule {
    id     = "abort-incomplete-mpu"
    status = "Enabled"
    abort_incomplete_multipart_upload { days_after_initiation = 7 }
  }

  rule {
    id     = "tier-and-expire"
    status = "Enabled"
    filter { prefix = "logs/" }
    transition { days = 30  storage_class = "STANDARD_IA" }
    transition { days = 90  storage_class = "GLACIER" }
    expiration { days = 365 }
    noncurrent_version_expiration { noncurrent_days = 30 }
  }
}

The mechanics of classes, lifecycle transitions, and their minimum-duration gotchas are covered in depth in S3 Storage Classes & Lifecycle Policies and S3 Versioning, Lifecycle & Replication.

Over-provisioned data & streaming services

Service Over-provision signal Right-size lever
RDS / Aurora Low CPU/connections; Multi-AZ on non-prod; gp2 storage Downsize class; Single-AZ for dev; gp3; Graviton r7g; RIs for steady DB
OpenSearch Low search/index rate; hot storage for old indices Smaller data nodes; UltraWarm/cold tiers; gp3; Graviton
Kinesis Data Streams Idle provisioned shards Switch to on-demand mode, or scale shards to throughput
DynamoDB Provisioned capacity far above consumed On-demand mode, or auto-scaling; Standard-IA table class; reserved capacity for steady
ElastiCache Low CPU/memory pressure Downsize nodes; Graviton r7g; reserved nodes
EMR Long-running clusters idle between jobs Transient clusters; Spot task nodes; auto-termination

The pattern across all of them: choose the right capacity model (on-demand vs provisioned) before you tune the size, use Graviton classes where offered, migrate storage to gp3, and buy reserved capacity only for the steady floor after right-sizing.

NAT and data-transfer reduction

Data transfer and NAT are the “invisible” meters — they do not appear as resources in a console list, only as usage types in Cost Explorer. Reduce them structurally:

Lever What it cuts How
Gateway VPC endpoints (S3/DynamoDB) NAT data-processing + egress for S3/DDB Free; traffic never touches NAT
Interface VPC endpoints (PrivateLink) NAT/egress to AWS APIs ~$0.01/hr + data, still cheaper than NAT at volume
Consolidate NAT gateways Per-AZ NAT hours One NAT for non-prod vs one-per-AZ
Keep traffic same-AZ Cross-AZ $0.01/GB each way AZ-aware routing, topology-aware hints
CloudFront / caching Internet egress from origin Cache at edge; 1 TB/mo free tier
Compress / batch Egress volume gzip, columnar formats, fewer round-trips
VPC endpoints for CI/registries ECR/S3 pull egress via NAT Interface/gateway endpoints

Endpoint mechanics live in AWS PrivateLink & VPC Endpoints and NAT sizing in NAT Gateway & Private-Subnet Egress.

Architecture as a cost lever

The deepest savings are structural: change the cost model, not just the size. This is lever four — highest effort, longest horizon, biggest ceiling — because it converts fixed, always-on cost into pay-per-use.

Pattern Cost model shift Saving mechanism Trade-off
Serverless (Lambda/Fargate) Per-request / per-second vs 24×7 No idle cost; scales to zero Cold starts; per-invoke cost at high volume
Auto Scaling to demand Track the curve vs peak-provision Pay for the actual load, not the peak Needs headroom + warm-up tuning
Scheduled scale-down Off-hours = off Stop non-prod nights/weekends (~65% of a week) Startup automation, state handling
Caching (ElastiCache/CloudFront) Serve from cache vs recompute/re-fetch Fewer origin calls, less egress, smaller DB Cache invalidation complexity
CDN offload Edge egress vs origin egress Cheaper egress + fewer origin resources Cache-key/TTL design
Spot fleets for batch Interruptible vs on-demand Up to 90% off Checkpoint/retry engineering
Right storage for the job Object/IA vs block/premium Match durability+access to price Access-pattern analysis
Managed vs self-managed Pay for service vs run infra Less ops + often cheaper at scale Less control

Two structural wins dominate in practice. First, auto-scaling and scheduled scale-down: a non-production fleet that runs 24×7 wastes ~65% of every week (168 hours, ~110 of them nights/weekends). Scheduling it off is a near-instant two-thirds cut with no re-architecture — see EC2 Auto Scaling & Launch Templates. Second, caching and CDN: every request served from CloudFront or ElastiCache is a request that did not hit (and did not need capacity on) your origin, and edge egress is cheaper than origin egress with the first 1 TB/mo free.

Architecture at a glance

The diagram traces cost optimization as a left-to-right loop, not a one-shot cut. Start at INFORM · SPEND (badge 1): you cannot cut what you cannot see, so you tag every resource, activate cost-allocation tags, and treat the hourly CUR as the source of truth, with Cost Explorer as the UI on top — a big untagged bucket is a red flag you fix before optimizing. Then you pull the four Optimize levers in order. RIGHT-SIZE first: Compute Optimizer (badge 2) reads ~14 days of CloudWatch to flag over/under-provisioned EC2, ASG, EBS, Lambda and ECS, and Graviton (badge 3) buys ~20% price-performance on ARM — do this first because it shrinks the baseline you are about to commit to. Only then COMMIT (badge 4): cover the steady floor with a Compute Savings Plan (up to 66%) or EC2 Instance SP (up to 72%), push interruptible work to Spot (up to 90% off), and watch coverage vs utilization so you commit to the baseline, not the peak.

Continuously, ELIMINATE WASTE (badge 5): sweep idle and unattached resources, migrate gp2→gp3, set S3 lifecycle + Intelligent-Tiering, abort incomplete multipart uploads, and cut log retention. Finally ARCHITECT · OPERATE (badge 6): serverless and auto-scaling turn fixed cost into pay-per-use and caching/CDN cut egress — then you Operate the loop with showback/chargeback, budgets, anomaly alerts, and unit economics (cost per request), feeding results back to Inform. Read the legend as symptom · confirm · fix for each numbered lever.

AWS FinOps cost-optimization loop showing spend flowing left to right through an Inform stage anchored on the Cost and Usage Report as source of truth, into four Optimize levers — right-sizing with Compute Optimizer and Graviton, committing with Savings Plans, Reserved Instances and Spot, eliminating waste including unattached EBS, gp2-to-gp3 and S3 lifecycle, and architecting for pay-per-use with serverless, auto-scaling and caching — arriving at realized savings and lower unit cost in an Operate stage that loops back, annotated with six numbered badges for inform-first, Compute-Optimizer right-sizing, Graviton price-performance, baseline commitment coverage-versus-utilization, continuous waste elimination, and the architect-and-operate loop

Real-world scenario

LedgerLoop, a 40-person fintech SaaS in Pune, was spending $48,000/month on AWS and growing the bill 8% month-over-month while revenue grew 5% — the classic “cloud outpacing the business” signal that gets a CFO’s attention. They had never run a structured optimization; the estate was two years of accreted decisions. The mandate: cut 30% in a quarter, without a reliability regression, and prove it per-team.

They ran the loop in order. Inform first: a two-week tagging sprint (a tag policy enforcing Team, Environment, Service, backfilled with a script), cost-allocation tags activated, and the CUR wired into Athena. That alone was revelatory: 31% of spend was untagged, and once attributed, one “experimental” data pipeline nobody owned was costing $5,100/month — it was switched off that week (−$5,100).

Right-size second. Compute Optimizer flagged 62% of the EC2 fleet over-provisioned — a legacy of copy-pasted m5.2xlarge launch templates. They downsized in waves behind their load balancers, after installing the CloudWatch agent so they had memory data (one memory-bound m5.xlarge was correctly left alone — right-sizing it on CPU would have OOM’d it). They modernized m5→m6i and moved the stateless web tier and their ElastiCache and RDS to Graviton (m7g, r7g), netting ~22% on those. Right-sizing + Graviton cut compute ~$9,800/month.

Commit third — deliberately after right-sizing. With the fleet now smaller and stable, ce get-savings-plans-purchase-recommendation sized a 1-year No-Upfront Compute Savings Plan covering ~78% of the new baseline. Because they committed to the right-sized fleet, utilization held at 98% — had they bought three months earlier at the old size, they would have stranded a third of it. SP savings: ~$6,400/month. Their nightly Spark jobs moved to Spot task nodes on EMR (−$1,900).

Eliminate waste, continuously. The sweep found 1.4 TB of unattached gp3, 380 orphaned snapshots, 14 unassociated EIPs, 9 idle ALBs from deleted services, and every bucket on Standard with no lifecycle. gp2→gp3 where any remained, lifecycle rules to Glacier + abort-incomplete-MPU, and CloudWatch Logs retention capped at 30 days trimmed another $3,600/month.

The quarter’s result: $48,000 → $30,900/month, a 36% cut, verified per-team via tags. Then the Operate discipline that made it stick: a Compute-SP utilization budget alerting below 95%, coverage below 75%, Cost Anomaly Detection per service, and a unit-cost KPI — cost per 1,000 transactions — on the engineering dashboard. Six months later the absolute bill had grown with the business, but unit cost was down 29%, which is the number that actually proves optimization is working.

Advantages and disadvantages

Advantages Disadvantages
Right-sizing compounds into every other lever Under-provisioning risks performance / OOM
Commitments cut the baseline 40–72% Commitments strand if you shrink after buying
Spot delivers up to ~90% off Spot needs fault-tolerant engineering
Waste elimination is low-risk, fast ROI Waste regenerates — needs continuous sweep
Graviton ~20% price-perf, often one-line ARM recompile / native-dependency work
Architecture converts fixed cost to pay-per-use Re-architecture is high effort, long horizon
FinOps loop makes savings durable, per-team Requires tagging discipline + org buy-in
Unit economics scale with the business Absolute cost still grows as you grow

The judgment: the levers are not equal and not simultaneous. For most estates, right-sizing + waste elimination are the fast 20–30%, commitments are the durable 15–25% on the now-right-sized baseline, and architecture is the multi-quarter structural play. Never let the visible discount of a commitment tempt you into buying before you have right-sized — the stranded-commitment failure is more expensive than the discount you were chasing.

Hands-on lab

You will (A) enroll and pull Compute Optimizer recommendations, (B) run a Savings Plans purchase recommendation and read coverage/utilization, © sweep for unattached EBS, old snapshots, unassociated EIPs, and gp2→gp3, (D) set a cost Budget with an action, and (E) codify the guardrails in Terraform, then tear down. Read-only analysis costs nothing; the ce API is $0.01/call.

⚠️ Cost note: Compute Optimizer, Cost Anomaly Detection, and the first two Budgets are free. Each ce API call is $0.01. modify-volume (gp2→gp3), deleting volumes/snapshots, and releasing EIPs are real changes — only run them on resources you have confirmed are waste. Do not buy a Savings Plan in this lab; we only recommend.

Part A — Compute Optimizer right-sizing recommendations

# Enroll (idempotent). From the org management account add --include-member-accounts
aws compute-optimizer update-enrollment-status --status Active
aws compute-optimizer get-enrollment-status --query '{Status:status,Resources:numberOfMemberAccountsOptedIn}'

# Over-provisioned EC2 with current → recommended and the monthly saving
aws compute-optimizer get-ec2-instance-recommendations \
  --filters name=Finding,values=Overprovisioned \
  --query 'instanceRecommendations[].{Name:instanceName,Cur:currentInstanceType,
           Rec:recommendationOptions[0].instanceType,
           Risk:recommendationOptions[0].performanceRisk,
           SavingsPct:recommendationOptions[0].savingsOpportunity.savingsOpportunityPercentage}' \
  --output table

# EBS: surfaces gp2→gp3 and over-provisioned IOPS
aws compute-optimizer get-ebs-volume-recommendations \
  --query 'volumeRecommendations[].{Cur:currentConfiguration.volumeType,
           Rec:volumeRecommendationOptions[0].configuration.volumeType,
           SavePct:volumeRecommendationOptions[0].savingsOpportunity.savingsOpportunityPercentage}' \
  --output table

Expected: a table of instances whose finding is Overprovisioned, each with a smaller recommended type and a projected savings percentage. NotEnoughData means < 30 hours of metrics — wait, or note the memory gap (install the CloudWatch agent for memory-aware recommendations).

Part B — Savings Plans recommendation, coverage, and utilization

# What AWS recommends you commit (1yr, no-upfront Compute SP, 30-day lookback)
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' \
  --output table

# If you already own commitments: are you using them, and how much is covered?
aws ce get-savings-plans-utilization \
  --time-period Start=2026-06-01,End=2026-07-01 \
  --query 'Total.Utilization.UtilizationPercentage' --output text

aws ce get-savings-plans-coverage \
  --time-period Start=2026-06-01,End=2026-07-01 \
  --granularity MONTHLY \
  --query 'Total.Coverage.CoveragePercentage' --output text

Expected: the recommendation shows an HourlyCommitmentToPurchase, an EstimatedMonthlySavingsAmount, and an EstimatedSavingsPercentage. Utilization should read ≥ 95% if you already own a well-sized plan; coverage in the 70–85% band means you are covering the baseline without over-committing.

Part C — Sweep for waste

# 1) Unattached EBS volumes (billing while attached to nothing)
aws ec2 describe-volumes --filters Name=status,Values=available \
  --query 'Volumes[].{Id:VolumeId,GiB:Size,Type:VolumeType,AZ:AvailabilityZone,Created:CreateTime}' \
  --output table

# 2) Snapshots you own older than a cutoff (candidate for a retention policy)
aws ec2 describe-snapshots --owner-ids self \
  --query 'Snapshots[?StartTime<=`2026-01-01`].{Id:SnapshotId,GiB:VolumeSize,Started:StartTime}' \
  --output table

# 3) Unassociated Elastic IPs (idle EIP charge)
aws ec2 describe-addresses \
  --query 'Addresses[?AssociationId==null].{IP:PublicIp,Alloc:AllocationId}' --output table

# 4) gp2 volumes still around (each ~20% pricier than gp3)
aws ec2 describe-volumes --filters Name=volume-type,Values=gp2 \
  --query 'Volumes[].{Id:VolumeId,GiB:Size,IOPS:Iops}' --output table

# --- Remediate ONLY confirmed waste ---
# gp2 → gp3 live (small volume, 3000 IOPS baseline is enough)
aws ec2 modify-volume --volume-id vol-0EXAMPLE --volume-type gp3
# Release an idle EIP
aws ec2 release-address --allocation-id eipalloc-0EXAMPLE
# Snapshot then delete an unattached volume (safety first)
aws ec2 create-snapshot --volume-id vol-0EXAMPLE --description "pre-delete safety"
aws ec2 delete-volume --volume-id vol-0EXAMPLE

Expected: four inventories of candidate waste. Confirm each is genuinely unused (owner, last-attach, application) before deleting — the modify-volume gp2→gp3 is safe and reversible-in-effect, but delete-volume/release-address are not.

Part D — A budget with an action (the backstop)

Create a monthly cost budget that, on breach, applies a restrictive IAM policy for approval (the closest thing to a stop):

ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

cat > budget.json <<EOF
{ "BudgetName":"finops-monthly-cap","BudgetLimit":{"Amount":"35000","Unit":"USD"},
  "TimeUnit":"MONTHLY","BudgetType":"COST" }
EOF

cat > notif.json <<EOF
[ { "Notification":{"NotificationType":"ACTUAL","ComparisonOperator":"GREATER_THAN",
      "Threshold":90,"ThresholdType":"PERCENTAGE"},
    "Subscribers":[{"SubscriptionType":"EMAIL","Address":"finops@example.com"}] } ]
EOF

aws budgets create-budget --account-id "$ACCOUNT_ID" \
  --budget file://budget.json --notifications-with-subscribers file://notif.json

aws budgets describe-budget --account-id "$ACCOUNT_ID" --budget-name finops-monthly-cap \
  --query 'Budget.{Name:BudgetName,Limit:BudgetLimit.Amount}' --output table

Expected: the budget lists with a $35,000 limit and a 90% actual alert.

Part E — Guardrails as Terraform

terraform { required_providers { aws = { source = "hashicorp/aws" } } }
provider "aws" { region = "ap-south-1" }

# Enroll Compute Optimizer via the API (no native resource; CLI-in-Terraform)
resource "null_resource" "compute_optimizer_enroll" {
  provisioner "local-exec" {
    command = "aws compute-optimizer update-enrollment-status --status Active"
  }
}

# Coverage + utilization guardrail budgets (alert on commitment drift)
resource "aws_budgets_budget" "sp_utilization" {
  name         = "sp-utilization-floor"
  budget_type  = "SAVINGS_PLANS_UTILIZATION"
  limit_amount = "95"
  limit_unit   = "PERCENTAGE"
  time_unit    = "MONTHLY"
  notification {
    comparison_operator        = "LESS_THAN"
    threshold                  = 95
    threshold_type             = "PERCENTAGE"
    notification_type          = "ACTUAL"
    subscriber_email_addresses = ["finops@example.com"]
  }
}

# Anomaly detection so a spike is caught in hours, not on the invoice
resource "aws_ce_anomaly_monitor" "services" {
  name              = "all-services"
  monitor_type      = "DIMENSIONAL"
  monitor_dimension = "SERVICE"
}

resource "aws_ce_anomaly_subscription" "daily" {
  name             = "anomalies-daily"
  frequency        = "DAILY"
  monitor_arn_list = [aws_ce_anomaly_monitor.services.arn]
  subscriber { type = "EMAIL"  address = "finops@example.com" }
  threshold_expression {
    dimension {
      key           = "ANOMALY_TOTAL_IMPACT_ABSOLUTE"
      values        = ["500"]
      match_options = ["GREATER_THAN_OR_EQUAL"]
    }
  }
}

Part F — Teardown

aws budgets delete-budget --account-id "$ACCOUNT_ID" --budget-name finops-monthly-cap
terraform destroy   # removes the guardrail budgets + anomaly monitor
# Leave Compute Optimizer enrolled (free) and any real gp3 migrations (they save money)

Expected: the budget and Terraform-managed guardrails are gone. Do not “undo” the gp2→gp3 migration or the waste deletions — those are the actual savings.

Common mistakes & troubleshooting

This is the playbook — the failure modes that turn a cost-optimization project into a regression or a stranded commitment. Scannable table first, then the worst ones in full.

# Symptom Root cause Confirm (exact cmd / console path) Fix
1 Right-sized instance now OOM-kills the app Downsized on CPU; memory not in default metrics No mem_used_percent in CW; app logs show OOM/killed Install CloudWatch agent; upsize; keep 20–30% mem headroom
2 Savings Plan utilization dropped to 60% Over-committed, or right-sized/ended workload after buying ce get-savings-plans-utilization < 95% Buy to the baseline; let the plan expire; add Compute SP flexibility next time
3 Coverage is 40%, still paying lots on-demand Under-committed ce get-savings-plans-coverage low; big on-demand line Buy an additional SP to the steady floor
4 RI applies nothing / wrong instances RI in wrong region/AZ/family, or zonal not regional ce get-reservation-utilization low; RI scope zonal Use Convertible RI + exchange; prefer regional; or use a Compute SP
5 Spot workload keeps failing / data loss Stateful/non-checkpointed job on Spot Interruptions in describe-spot-instance-requests; app state lost Move to on-demand/SP; only checkpointed/stateless on Spot
6 gp2→gp3 tanked disk performance gp2 had > 3,000 baseline IOPS (large vol) or relied on burst describe-volumes old gp2 IOPS > 3,000; high latency now Provision matching --iops/--throughput on gp3
7 Deleted an “idle” volume/EIP that was in use Judged idle by name, not by metrics/owner App breaks; resource was attached intermittently Confirm via metrics + owner tag before delete; snapshot first
8 Can’t attribute 30% of the bill to a team Resources untagged; tags not activated Cost Explorer group-by tag shows big “No tag key” Tag policy + backfill; activate cost-allocation tags
9 Anomaly alerts ignored (alert fatigue) Threshold too low / individual frequency on noisy spend Inbox full of tiny anomalies Raise $ threshold; switch to daily digest; scope monitors
10 Compute Optimizer shows NotEnoughData < 30 hrs metrics or unsupported resource get-ec2-instance-recommendations finding empty Wait for data; enable enhanced metrics; check support
11 Modernized generation, instance won’t boot Old AMI lacks ENA/NVMe for new gen Launch fails / no network on m6i+ Use a current AMI with ENA + NVMe drivers
12 Graviton migration: binary won’t run x86 artifact on arm64 exec format error; image linux/amd64 only Build arm64 / multi-arch image; rebuild native deps
13 Savings “vanished” next quarter No Operate loop; waste regrew Bill back up; new unattached/idle resources Continuous sweep automation; unit-cost KPI; reviews
14 Bill flat but growing per-customer Optimizing absolute cost, not unit cost Unit-cost KPI trending up Track $/request or $/customer; optimize the unit
15 Auto-scaling never scales down (cost stays high) Scale-in blocked / cooldown / min too high ASG activity shows no scale-in Fix scale-in policy, cooldown, min size; scheduled scale-down

The four that cost the most, in full:

1 — right-sized into an out-of-memory crash. The most damaging right-sizing failure is downsizing a memory-bound instance on CPU signal alone. The EC2 hypervisor cannot see guest RAM, so memory is not a default CloudWatch metric and Compute Optimizer’s default recommendation is CPU/network/EBS-weighted. Downsize a JVM heap or an in-memory cache on “low CPU” and it OOM-kills under load. Always install the CloudWatch agent (or an APM) to publish mem_used_percent, feed it into the decision (or enable Compute Optimizer’s memory support), and keep 20–30% headroom on the constraining dimension. Right-sizing is choosing the smallest that survives the p99 with headroom — not the smallest that fits the average.

2 & 3 — the stranded or under-covered commitment. This is the failure that inverts the whole project. Utilization answers “am I using what I bought” (target ≥ 95%); coverage answers “how much of my usage is on a commitment” (target ~70–85%). Buy before right-sizing, or buy to the peak instead of the baseline, and the moment you shrink the fleet your utilization collapses — you now pay for reserved capacity you do not use, and the discount is worse than on-demand would have been. Confirm with ce get-savings-plans-utilization and ...coverage. The fix is procedural: right-size first, then commit to the 24×7 floor so utilization stays near 100% while the variable top rides on-demand/Spot, and prefer Compute Savings Plans (or Convertible RIs) so a later re-architecture does not strand the commitment.

5 — Spot on the wrong workload. Spot’s up-to-90% discount is for interruptible work: stateless web behind a load balancer, checkpointed batch/ML, retryable CI, queue workers where the queue re-delivers. Put a stateful single-node database or a long non-checkpointed job on Spot and a reclaim (2-minute notice) is data loss or hours of lost work. Confirm suitability before, not after: does the work survive a node vanishing mid-flight? If not, it belongs on on-demand or a Savings Plan. The mature answer is a mixed-instances group — on-demand/SP base for the floor, Spot across many types/AZs for the top, with capacity-rebalancing.

6 — gp2→gp3 without checking IOPS. gp2 delivered 3 IOPS/GB (plus burst for small volumes), so a 1,000 GB+ gp2 volume had ≥ 3,000 baseline IOPS and a small volume leaned on burst credits. gp3 starts at a flat 3,000 IOPS / 125 MB/s free — great for most, but if you blindly modify-volume --volume-type gp3 on a large or burst-reliant gp2, you drop IOPS/throughput and latency spikes. Check the old gp2 IOPS with describe-volumes and provision matching --iops/--throughput on gp3 (still ~20% cheaper at equal performance). Never assume the free baseline is enough for a big volume.

CLI error / status reference

Code / status Where Meaning Fix
OptInRequiredException compute-optimizer Account not enrolled update-enrollment-status --status Active
NotEnoughData (finding) compute-optimizer < 30 hrs metrics / unsupported Wait; enable enhanced metrics
DataUnavailableException ce API Cost Explorer not enabled / not ready Enable CE; wait ~24h
AccessDeniedException ce/budgets/compute-optimizer Missing billing IAM perms Grant ce:*,budgets:*,compute-optimizer:*
VolumeModification ... not allowed ec2 modify-volume Modified again within 6h, or unsupported Wait 6h between modifications
IncorrectState ec2 delete-volume Volume still attached Detach first; confirm not in use
AuthFailure/InvalidAddress ec2 release-address EIP already released / wrong ID Re-list with describe-addresses
exec format error runtime (Graviton) x86 binary on arm64 Build arm64/multi-arch artifact
ThrottlingException ce API Too many calls ($0.01 each) Back off; cache results
ValidationException (budget) budgets Bad budget/notification JSON Fix schema (ThresholdType, etc.)

Anti-patterns

Anti-pattern Why it fails Do instead
Commit before right-sizing Strands the commitment when you shrink Right-size, then commit to the new baseline
Buy to the peak Utilization collapses off-peak Commit to the 24×7 floor only
3yr All-Upfront on a new workload Locks an uncertain baseline for 3 years 1yr No-Upfront Compute SP first
Chase the biggest % discount EC2 Instance SP/Standard RI lock you in Weigh flexibility; Compute SP for changing shape
Optimize absolute cost only Grows with the business regardless Track unit cost ($/request, $/customer)
One-time cleanup Waste regenerates Continuous, automated sweep
Delete by name/hunch Kills something in use Confirm via metrics + owner tag + snapshot
Right-size on CPU only Memory-bound OOM Include memory; keep headroom

Decision table — which lever do I reach for?

If you want to… Reach for Why
Cut cost with zero commitment Right-size + waste sweep No lock-in, fast ROI
Cut the steady 24×7 baseline Compute/EC2 Instance Savings Plan 40–72% on predictable load
Cut an interruptible batch job Spot Up to ~90% off
Keep flexibility to change shape Compute SP / Convertible RI Applies across family/region
Reserve capacity in a specific AZ Zonal RI Guarantees launch
Cut storage cost gp2→gp3, S3 lifecycle/Int-Tiering ~20% + tiering
Cut egress/NAT VPC endpoints, caching, CDN Removes the invisible meter
Make savings durable FinOps Operate loop + unit KPIs Prevents regrowth

Best practices

Security notes

Cost optimization touches production capacity and reveals sensitive business signal — treat it with the same rigor as any change.

Concern Guidance
Least privilege for FinOps Grant ce:*, compute-optimizer:*, budgets:*, cur:* and read-only ec2:Describe* to analysts — separate from resource-mutating admin
Right-sizing is a change Downsizing/modifying volumes are production changes — go through change control, canary behind LBs, keep rollback (snapshots/AMIs)
Budget action policies are powerful An IAM/SCP a budget action applies can lock out a team — scope tightly, prefer approval-required over auto-apply
Cost data is sensitive Spend reveals architecture, scale, launch timing — restrict Cost Explorer/CUR to those who need it
CUR S3 bucket Block public access, encrypt (SSE-S3/KMS), restrict the bucket policy to billingreports.amazonaws.com and analysts
Deletion safety Snapshot before deleting volumes; require owner-tag confirmation before terminating “idle” resources; use recycle-bin/retention where available
Cross-account visibility In an org, members see only their own cost unless the payer shares — don’t leak org-wide spend to one team
Audit the changes Right-sizing, deletions, and commitment purchases are logged in CloudTrail — keep the trail for accountability

Cost & sizing

The optimization tooling is nearly free; the savings are large. What the tools cost:

Item Cost Notes
Compute Optimizer Free (base) Enhanced infrastructure metrics (3-mo lookback) is a small per-metric-hour add-on
Cost Explorer UI Free ce API is $0.01/request — cache scripted pulls
Cost Anomaly Detection Free Monitors + subscriptions cost nothing
AWS Budgets First 2 free, then $0.02/budget/day Utilization/coverage budgets count too
CUR S3 storage + Athena scan Parquet + partitioning keeps scans cheap
Trusted Advisor (cost checks) Full checks need Business/Enterprise Support Core checks free
Savings Plans / RIs The commitment itself The savings mechanism, not a fee

Realistic magnitudes (INR/USD): the tooling for a full FinOps practice — Compute Optimizer, anomaly detection, a handful of budgets, a CUR in Athena — is effectively ₹0 to a few hundred rupees a month. The savings on a mid-size estate are transformative: right-sizing + waste elimination commonly recovers 15–30% of the bill, commitments another 15–25% of the right-sized compute, and Graviton ~20% of migrated workloads. On a $48,000/month estate that is $14,000–17,000/month — the tooling cost is a rounding error against it. The sizing rule: spend near-zero on the tooling, spend your effort on the sequence — right-size before you commit, sweep waste continuously, and run the loop so it does not grow back.

Interview & exam questions

1. Why must right-sizing precede buying commitments? (SAA-C03, SAP) — Because a Savings Plan or RI is sized to your usage baseline. If you commit first and then right-size (shrink) the fleet, your commitment utilization collapses — you pay for reserved capacity you no longer use, stranding the discount. Right-size to the real baseline, then commit to that smaller floor.

2. Compare Compute, EC2 Instance, and SageMaker Savings Plans. (SAA-C03) — Compute SP applies to EC2 (any family/region/OS), Fargate, and Lambda with the most flexibility, up to ~66% off. EC2 Instance SP locks to one family in one region for ~6 more points (up to ~72%). SageMaker SP covers SageMaker ML instances (up to ~64%). Choose Compute SP for flexibility, EC2 Instance SP for a stable known-family baseline.

3. What is the difference between coverage and utilization? (SOA-C02, SAP) — Utilization is the percentage of a commitment you actually consume (target ≥ 95%; low means you over-committed and stranded spend). Coverage is the percentage of eligible usage sitting on a commitment (target ~70–85%; low means you are under-committed and leaving discount unclaimed). You commit to the baseline so utilization stays high while coverage sits below the peak.

4. Standard vs Convertible RIs — when each? (SAA-C03) — Standard RIs give a higher discount (up to ~72%) but cannot change instance family/OS/tenancy (only modify size within family, and can be sold in the Marketplace). Convertible RIs (up to ~66%) can be exchanged for a different configuration. Use Standard when the config is certain for the term; Convertible when it may evolve. For pure EC2 flexibility, a Compute Savings Plan usually beats both.

5. Regional vs zonal RIs? (SAA-C03, ANS) — A regional RI gives a billing discount with no capacity guarantee but with AZ and size flexibility (via normalization factors) across the region. A zonal RI reserves capacity in a specific AZ (guarantees you can launch) but has no size flexibility. Use zonal only when you need a capacity guarantee in an AZ.

6. Why can right-sizing an instance cause an out-of-memory crash? (SOA-C02) — Because memory is not a default CloudWatch/EC2 metric (the hypervisor can’t see guest RAM), so a CPU-only right-sizing decision ignores memory pressure. Downsizing a memory-bound workload OOM-kills it. Install the CloudWatch agent for mem_used_percent and keep headroom.

7. What is the price-performance benefit of Graviton, and what is the migration cost? (SAA-C03) — Graviton (ARM64) typically delivers ~20% better price-performance (up to ~40% on some workloads). The migration cost is architecture: binaries must be recompiled for arm64 and container images built multi-arch; interpreted languages and managed services (RDS, Lambda, Fargate) are often near-zero-effort switches.

8. When is Spot the wrong choice? (SAA-C03, SOA-C02) — When the workload cannot tolerate a 2-minute interruption: stateful single-node databases, long non-checkpointed jobs, or anything where losing the node loses data or hours of work. Spot fits stateless, checkpointed, retryable, fault-tolerant workloads, ideally in a mixed-instances group with an on-demand base.

9. What must you check before migrating gp2 to gp3? (SOA-C02) — The gp2 volume’s baseline IOPS/throughput. gp2 gives 3 IOPS/GB (plus burst), so a large gp2 (≥1,000 GB) had ≥3,000 baseline IOPS. gp3’s free baseline is 3,000 IOPS / 125 MB/s; for larger or burst-reliant volumes you must provision matching IOPS/throughput on gp3 (still ~20% cheaper at equal performance).

10. Describe the FinOps Inform → Optimize → Operate loop. (SAP) — Inform: establish visibility and allocation (tagging, cost-allocation tags, CUR, showback). Optimize: reduce and commit (right-size, buy SP/RI, kill waste, re-architect). Operate: govern continuously (budgets, anomaly detection, unit-cost KPIs, reviews) and feed back into Inform. It is a loop because waste regenerates and the baseline changes.

11. Why track unit economics instead of absolute cost? (SAP) — Because absolute cost grows with the business even when you are optimizing well. Unit cost — cost per customer, per request, per GB — normalizes for growth, so a falling unit cost proves optimization is working even as the total bill rises. It is the metric that ties engineering cost to business value.

12. Your Savings Plan utilization dropped to 60% after a migration — what happened and what do you do? (SOA-C02, SAP) — A workload the plan was covering was right-sized, moved (e.g. to Graviton or another region an EC2 Instance SP didn’t cover), or ended, so less eligible usage matches the commitment. If it was a Compute SP it will re-apply broadly; if EC2 Instance SP the family/region lock is the issue. Let the plan run down, buy future commitments as flexible Compute SPs sized to the new baseline, and avoid re-committing until the estate is stable.

Quick check

  1. You are told to “cut 30% this quarter” and the RI discount looks huge. What must you do before buying any commitment, and why?
  2. Your Compute Savings Plan shows 98% utilization but only 45% coverage. Are you over- or under-committed, and what is the fix?
  3. A teammate downsized a Redis cache instance because CPU was low; it now crashes under load. What signal did they miss and how do you avoid it?
  4. Which workloads are safe on Spot, and name one that is not?
  5. Before running modify-volume --volume-type gp3 on a 2 TB gp2 volume, what one thing must you check?

Answers

  1. Right-size first. A commitment is sized to your baseline; if you buy at today’s (over-provisioned) size and then right-size down, utilization collapses and you pay for reserved capacity you no longer use — the stranded-commitment trap. Right-size, then commit to the smaller, real baseline.
  2. Under-committed. Utilization (98%) is healthy — you are using what you bought — but coverage (45%) means more than half your eligible usage is still on-demand. Buy an additional Savings Plan to raise coverage toward the 70–85% baseline band.
  3. Memory. Memory is not a default CloudWatch metric, so a CPU-only decision missed that the cache was memory-bound. Install the CloudWatch agent (mem_used_percent) or enable Compute Optimizer memory support, size on the constraining dimension, and keep 20–30% headroom.
  4. Safe: stateless web behind a load balancer, checkpointed batch/ML, retryable CI, queue workers (queue re-delivers). Not safe: a stateful single-node database or a long non-checkpointed job — a 2-minute reclaim means data or work loss.
  5. The gp2 baseline IOPS/throughput. A 2 TB gp2 had ~6,000 baseline IOPS (3 IOPS/GB); gp3’s free baseline is only 3,000 IOPS / 125 MB/s. You must provision matching --iops/--throughput on gp3 or performance regresses (it is still ~20% cheaper at equal performance).

Glossary

Term Definition
FinOps Operational practice bringing financial accountability to variable cloud spend across engineering, finance, and product
Inform → Optimize → Operate The three iterative FinOps phases: visibility, action, continuous governance
Right-sizing Matching resource size/type to actual utilization; the first and most compounding lever
Compute Optimizer AWS service that reads CloudWatch metrics and recommends right-sized EC2/ASG/EBS/Lambda/ECS resources
Graviton AWS ARM64 processors (Graviton2/3/4) delivering ~20% better price-performance than equivalent x86
Savings Plan A 1/3-year $/hour spend commitment traded for a discount; Compute (flexible), EC2 Instance (family+region), or SageMaker
Reserved Instance A 1/3-year capacity/billing reservation; Standard (higher discount) or Convertible (exchangeable); regional or zonal
Spot Spare EC2 capacity at up to ~90% off, reclaimed with 2-minute notice; for interruptible workloads
Coverage Percentage of eligible usage sitting on a commitment (target ~70–85%)
Utilization Percentage of a commitment actually consumed (target ≥ 95%); low = stranded
Normalization factor The size “footprint” (nano 0.25 … 2xlarge 16) that lets a regional RI float across sizes in a family
gp3 Current general-purpose SSD EBS type; ~20% cheaper than gp2 with 3,000 IOPS / 125 MB/s free baseline
Intelligent-Tiering S3 storage class that auto-moves objects between access tiers with no retrieval fees
Showback / Chargeback Reporting cost to a team (showback) vs actually billing it back (chargeback)
Unit economics Cost per business unit (customer, request, GB) — the metric that scales with the business
CUR Cost and Usage Report — hourly, resource-level line-item export to S3; the source of truth
Amortized cost Upfront commitment fees spread across the term to show a true per-hour run-rate

Next steps

You can now run cost optimization as a sequenced, continuous loop rather than a one-time cut. Extend outward:

AWSFinOpsCost OptimizationRight-SizingSavings PlansReserved InstancesCompute OptimizerGraviton
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments

Keep Reading