# EC2 Rightsizing

> Sizes low-CPU EC2 instances down one or two steps in the same family, with memory vetoes and real rate-delta savings.

Source: https://zop.dev/integrations/aws/recommendations/ec2-rightsizing

---

## An instance bills for its size, not its load

[EC2 On-Demand pricing](https://aws.amazon.com/ec2/pricing/on-demand/) is per instance type per
hour. Within a family each size step down usually halves vCPU, memory and price; in US East
(N. Virginia) an `m5.xlarge` is $0.192 an hour and an `m5.large` $0.096. An instance that averages a
few percent CPU all month is paying for capacity that sits idle.

AWS notes that when you change the instance type
[you start paying the rate of the new type](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-resize.html),
and it points to AWS Compute Optimizer for a type recommendation.

## Pulling CPU and memory yourself

```bash
aws cloudwatch get-metric-statistics --namespace AWS/EC2 --metric-name CPUUtilization \
  --dimensions Name=InstanceId,Value=i-0123456789abcdef0 \
  --statistics Average Maximum --period 86400 \
  --start-time 2026-08-26T00:00:00Z --end-time 2026-09-25T00:00:00Z

aws cloudwatch get-metric-statistics --namespace CWAgent --metric-name mem_used_percent \
  --dimensions Name=InstanceId,Value=i-0123456789abcdef0 \
  --statistics Average Maximum --period 86400 \
  --start-time 2026-08-26T00:00:00Z --end-time 2026-09-25T00:00:00Z
```

EC2 does not publish memory on its own. It comes from the
[CloudWatch agent](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/metrics-collected-by-CloudWatch-agent.html)
as a used-memory percentage (the metric in the second command), in the `CWAgent` namespace by default.

## How the downsize depth is chosen

| 30-day average CPU | Recommendation | Memory rule |
|---|---|---|
| Below 2% | Two sizes smaller, high severity | Blocked if memory, scaled to the smaller size, would exceed 80% |
| 2% to under 5% | One size smaller, medium severity | Blocked if memory is at 60% or more |
| 5% or more | Nothing | |

A CPU peak at or above 80% anywhere in the series also blocks the change. An instance stopped for
60% to 99% of the last 7 days gets a cautious one-size step.

## Instances the rule hands to other checks

An instance idle for 95% or more of at least 168 hourly readings in the 30 days is left to
<a href="https://zop.dev/integrations/aws/recommendations/idle-running-ec2-instance">Idle Running EC2 Instance</a>,
since stopping it beats shrinking it. A fully stopped instance belongs to
<a href="https://zop.dev/integrations/aws/recommendations/idle-ec2-instance">Idle EC2 Instance</a>. GPU families
are sized by
<a href="https://zop.dev/integrations/aws/recommendations/gpu-instance-low-utilization">GPU Instance Low Utilization</a>
on GPU metrics instead. With no memory data, memory-optimized families (r, x, z, u, i) and database
or broker hosts are skipped; other instances are sized on CPU alone and the finding says memory could
not be verified.

## Real rates for the current and target size

```text
saving = monthly cost x (1 - target hourly rate / current hourly rate)
```

Both are On-Demand catalog rates for real instance types. If there is no smaller type in the family
at the chosen depth, or no rate gap, nothing is shown.

## Resizing the instance

1. Confirm memory headroom with the agent metrics if they are not already installed.
2. Stop the instance, change its type, and start it:
   `aws ec2 modify-instance-attribute --instance-id i-0123456789abcdef0 --instance-type Value=m5.large`
3. Watch CPU, memory and latency for a few days.
4. Revisit after a month; a two-step case may be worth another step.

**Warning**
Changing the type needs a stop and start, so the instance is unavailable for a few minutes.
