SDP Clouds
← All posts
AWS·3 min read

Right-Sizing EC2 Before It Bites Your Bill

Burstable instances, credit balances, and CloudWatch graphs — how to pick an EC2 size you won't regret, and how to shrink one you already regret.


We ran a t3.medium for our staging environment for fourteen months. CPU sat at 4%. Nobody staged anything because deploys to staging took forever — turns out the burst credits were exhausted and the instance was crawling at 5% of baseline performance. Two problems, one instance, both caused by picking a size based on vibes.

Here's the process I use now, before buying and after.

Before you buy: match the instance to the workload

AWS instance families exist because different workloads bottleneck on different resources:

  • T-series (burstable) — general apps with spiky CPU. You earn credits when idle and spend them when busy. Great default for dev/staging, low-traffic sites, jump boxes. Dangerous when the workload is consistently busy — credits drain, performance floor drops.
  • M-series (general purpose) — steady mixed CPU/memory. Web servers, app tiers that never idle enough to burst comfortably.
  • C-series (compute) — CPU-bound work: build agents, video processing, high-throughput APIs.
  • R-series (memory) — caches, in-memory databases, heaps that want room.
  • Graviton (anything a) — ARM-based, meaningfully cheaper for the same capability, as long as your stack builds for ARM. Our container images multi-arch now and the savings were immediate.

If the workload is "I don't know yet," start t3.small or t3.medium for anything non-production and watch it for a week before committing.

The three graphs I look at before resizing

In CloudWatch, for the last 7 days:

  1. CPUUtilization (p95, not average — averages hide spikes). Under 15% p95 for a week suggests you're oversized.
  2. NetworkIn/NetworkOut — a "CPU-idle" instance that's network-saturated won't get faster with more vCPUs; it needs a different family or a bigger pipe.
  3. MemoryUtilization — not emitted by default for EC2. Install the CloudWatch agent, seriously. Guessing memory is how you get OOM kills at 3 AM.

For burstable instances specifically, also check CPUCreditBalance and CPUCreditUsage. If the balance sits at zero, you're throttled and either need unlimited mode (which bills when you overshoot), a bigger baseline (t3.large), or a non-burstable family.

Shrinking is where the money is

The uncomfortable truth from cost reviews I've run: most environments are 2–3x oversized because someone picked the instance during an outage and never revisited it.

My shrinking ritual:

  1. Confirm p95 CPU and memory for 14 days (two weeks covers any monthly job spikes).
  2. Resize during a maintenance window — stop, change instance type, start. New instance gets a new public IP unless you planned an Elastic IP or it sits behind an ALB — plan for that.
  3. Keep the old instance stopped (not terminated) for 48 hours. If everything's fine, terminate and reclaim the EBS volume.
aws ec2 stop-instances --instance-ids i-0abc123
aws ec2 modify-instance-attribute \
  --instance-id i-0abc123 \
  --instance-type '{"Value": "t3.small"}'
aws ec2 start-instances --instance-id i-0abc123

Don't forget what's around the instance

Right-sizing EC2 alone misses half the cost. The EBS volume sized "just in case" at 500 GP3 for a 20 GB filesystem. The unattached volume from an instance that got replaced. The Elastic IP with no instance attached — those bill now. I run this monthly:

aws ec2 describe-volumes \
  --filters Name=status,Values=available \
  --query 'Volumes[].{Id:VolumeId,Size:Size}'

Everything in that list is paying rent to do nothing.

The habit that prevents regret

Size decisions get a line in the runbook with the date and the reasoning: "t3.medium, staging, because p95 CPU was 22% at t3.small during load test." Six months later, that note turns a fifteen-minute resize into a five-second decision instead of another fourteen months of 4% CPU.

#aws#ec2#cost-optimization#cloud