EC2 Rightsizing and Graviton: Two Levers That Stack
EC2 rightsizing with CloudWatch and Compute Optimizer, a Graviton migration checklist, us-east-1 prices, and why both come before Savings Plans.
Most EC2 fleets we look at were sized once, at launch, and then left alone. An m5.xlarge got picked because the last service used one, the service went live, and nobody opened the graphs again. Three years later it’s still there, peaking at 11% CPU, and it’s been copied into staging, QA and two “temporary” load-test accounts.
Rightsizing and moving to Graviton are the two cheapest fixes for that, and they stack. Rightsizing shrinks how much instance you buy. Graviton lowers the price of each unit you keep. Do them in the right order and the commitments you buy afterwards are smaller and fit better, which is where the saving gets locked in.
Prices below are on-demand Linux list prices in us-east-1 at the time of writing, taken from AWS’s published price list. Your effective rate will be different if you already have Savings Plans, Reserved Instances or a private pricing agreement.
Start with what the instance actually does
CPU comes free, memory doesn’t
EC2 sends CPUUtilization to CloudWatch out of the box. It doesn’t send memory, because the hypervisor can’t see inside your guest OS. To get it you need the CloudWatch agent publishing mem_used_percent in the CWAgent namespace on Linux (or Available MBytes on Windows), with the InstanceId dimension kept intact.
This matters more than people expect. A Java service with a 12 GB heap can sit at 6% CPU all day. Downsize it on CPU alone and you’ll find out about memory from the OOM killer. Agent metrics are billed as custom metrics, so collect memory and leave the other forty counters off unless someone will actually read them.
Here’s a quick way to see which instances report memory at all. It’s read-only:
# Instances that publish memory through the CloudWatch agent
aws cloudwatch list-metrics --namespace CWAgent --metric-name mem_used_percent \
--query 'Metrics[].Dimensions[?Name==`InstanceId`].Value[]' --output text
# What you're running, by instance type
aws ec2 describe-instances --filters Name=instance-state-name,Values=running \
--query 'Reservations[].Instances[].InstanceType' --output text \
| tr '\t' '\n' | sort | uniq -c | sort -rn
The second list is usually the more interesting one. If the top rows are m5, c5 and r5, you’re paying older-generation prices for older-generation performance.
What “oversized” really looks like
A daily average hides the peaks that matter. Look at the hourly maximum over at least two weeks, and preferably a period that includes month-end or whatever your busy day is. The first date form below is GNU; the one after || is for macOS:
START=$(date -u -d '14 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-14d +%Y-%m-%dT%H:%M:%SZ)
aws cloudwatch get-metric-statistics --namespace AWS/EC2 --metric-name CPUUtilization \
--dimensions Name=InstanceId,Value=i-0123456789abcdef0 \
--start-time "$START" \
--end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--period 3600 --statistics Average Maximum --output table
An instance whose hourly peaks stay under about 40% CPU and whose memory stays under half is a candidate to drop one size. Check network and EBS throughput too. Smaller sizes have lower bandwidth ceilings, and a box that looks idle on CPU can be busy shovelling data.
Let Compute Optimizer do the arithmetic
Doing that by hand for 300 instances gets old fast. AWS Compute Optimizer is free to opt into and does the same analysis across the fleet. A few details worth knowing:
- It needs at least 30 hours of metrics in the past 14 days before it’ll recommend anything for an instance.
- The default lookback is 14 days. You can switch to 32 days at no cost, or 93 days if you pay for enhanced infrastructure metrics.
- You can tune it. CPU threshold options are P90, P95 or P99.5, and headroom can be 0%, 20% or 30% for CPU (10%, 20% or 30% for memory). The default is the cautious P99.5 with 20% headroom.
- Without memory metrics it simply doesn’t consider memory, so install the agent first. AWS lists the metrics Compute Optimizer analyses for EC2.
Findings come back as over-provisioned, under-provisioned or optimized. “Optimized” can still carry a recommendation to move to a newer generation, so don’t skip those rows. Pull the over-provisioned list from the CLI:
aws compute-optimizer get-ec2-instance-recommendations \
--filters name=Finding,values=Overprovisioned \
--query 'instanceRecommendations[].[instanceArn,currentInstanceType,recommendationOptions[0].instanceType,recommendationOptions[0].performanceRisk]' \
--output table
Add --recommendation-preferences cpuVendorArchitectures=AWS_ARM64 and the same call returns Graviton options only. The console has a matching “CPU architecture preference” dropdown and a migration effort column, which flags when a move crosses architectures and whether Compute Optimizer could infer the workload.
Use it as a second opinion. It doesn’t know about the quarterly batch run that falls outside its window, or that a vendor licence is tied to a core count. Treat high performance-risk recommendations as “go and look”, not “go and change”.
Newer generations: the hourly price isn’t the whole story
Here’s the same shape (2 vCPU, 8 GiB) across generations:
| Instance | Processor | On-demand $/hr | Per month (730 h) |
|---|---|---|---|
| m5.large | Intel Xeon | $0.0960 | $70.08 |
| m6i.large | Intel Xeon | $0.0960 | $70.08 |
| m7i.large | Intel Xeon | $0.1008 | $73.58 |
| m8i.large | Intel Xeon | $0.10584 | $77.26 |
| m6g.large | Graviton2 | $0.0770 | $56.21 |
| m7g.large | Graviton3 | $0.0816 | $59.57 |
| m8g.large | Graviton4 | $0.08976 | $65.52 |
| m9g.large | Graviton5 | $0.09784 | $71.42 |
Two things jump out. Within a generation, Graviton is cheaper per hour: m7g is about 19% below m7i, and m8g about 15% below m8i. And every new generation costs a bit more per hour than the last. You’re paying for faster cores. AWS says Graviton4-based M8g delivers up to 30% better performance than Graviton3-based instances, and that the new Graviton5-based M9g gives up to 25% better compute performance than M8g.
That speed only turns into savings if it lets you run a smaller size or fewer instances. A box that idles at 8% CPU will simply idle at a lower percentage on a faster chip. So rightsize first, then pick the generation, then load-test to see whether the faster cores let you trim the count.
Where Graviton stands in 2026
Graviton4 (M8g, C8g, R8g and friends) is the widely available workhorse. Graviton5 arrived as M9g and C9g in June 2026 and R9g at the end of August, initially in US East (N. Virginia and Ohio), US West (Oregon) and Europe (Frankfurt), with more regions being added. AWS’s own Graviton page says Graviton instances cost up to 20% less than comparable x86 instances and use up to 60% less energy for the same performance. We’d bank the price difference, which you can read off the price list, and treat the performance claims as something to confirm with your own load test.
A Graviton migration checklist
Most Linux workloads move without drama. The ones that don’t usually fail on something dull.
- List what can’t move. Graviton doesn’t run Windows. Check commercial software, security agents, APM agents and log shippers for arm64 builds. One missing agent can block a whole fleet.
- Check native dependencies. Interpreted and JIT languages (Python, Node.js, Java, Ruby, .NET on Linux) usually just work, except for native extensions and wheels. Go and Rust cross-compile with a flag. Use a current JDK rather than an old Java 8 build.
- Build multi-arch images.
docker buildx build --platform linux/amd64,linux/arm64produces one tag that runs on both. Confirm withdocker buildx imagetools inspect <image>. Build on arm64 runners if you can, since QEMU emulation is slow enough to make CI miserable. - Sort out AMIs and launch templates. You need an arm64 AMI. An Auto Scaling group can mix architectures by giving each instance type override its own launch template.
- Test on throughput per dollar. Compare p99 latency and requests per instance under production-like load. CPU percentage alone won’t tell you much across different chips.
- Roll out in slices. Put a small share of traffic on Graviton behind the load balancer, watch error rates for a week or two, and keep the x86 path ready to take traffic back.
AWS publishes a Graviton getting-started guide with language-specific notes, and it’s worth an hour of anyone’s time before step 2.
When not to bother: vendor software that’s x86-only, code with hand-tuned x86 intrinsics on the hot path, or a fleet so small that the engineering time costs more than a year of savings. Two t3.small instances don’t need a migration project.
Managed services are the easy wins
Some of the best Graviton savings involve no code changes at all.
- RDS and Aurora. MySQL, PostgreSQL and MariaDB on RDS support Graviton classes, as does Aurora. Oracle and SQL Server don’t. Switching is an instance class change, so do it in a maintenance window, with Multi-AZ to keep the interruption short. RDS added Graviton5 M9g classes in June 2026, and AWS cites up to 30% better performance and up to 23% better on-demand price/performance than Graviton4 of the same size, depending on engine and workload.
- ElastiCache. Clients talk to it over the network, so the CPU architecture is invisible to your code. Graviton node types such as cache.m8g and cache.r8g (Graviton4, added for Valkey and Memcached in August 2026) are the sensible starting point for new clusters; m7g and r7g remain fine for existing ones.
- Lambda. Setting the architecture to arm64 drops the first-tier duration price in us-east-1 from $0.0000166667 to $0.0000133334 per GB-second, which is 20% lower. Check layers, extensions and any native packages, and use alias weighted routing to send a slice of traffic to the arm64 version first.
- Fargate tasks can also run on ARM64 by setting the task’s CPU architecture, as long as the image has an arm64 build. A multi-arch image makes it easy to run both while you test.
Rightsize, migrate, then commit
Savings Plans and Reserved Instances discount whatever you commit to, including waste. Commit at today’s size and you lock in today’s oversizing for one or three years.
The architecture choice matters too. An EC2 Instance Savings Plan applies to one instance family in one region. Buy one for m5 and then move to m8g, and the commitment sits there with nothing to cover. Compute Savings Plans follow usage across families, sizes and architectures, which is why we lean on them during a migration year. Our Reserved Instances vs Savings Plans guide covers sizing the commitment properly.
A worked example. Say you have 20 m5.xlarge web servers whose peaks stay under 15% CPU and 30% memory:
| Step | Fleet | Monthly on-demand |
|---|---|---|
| Today | 20 × m5.xlarge | $2,803.20 |
| Rightsize | 20 × m5.large | $1,401.60 |
| Graviton | 20 × m8g.large | $1,310.50 |
The Graviton step looks small next to rightsizing here, partly because m5 is cheap per hour for its age. Notice that moving to m7i.large instead would have raised the bill to $1,471.68. The bigger Graviton gain comes if load testing shows the faster cores can carry the same traffic on fewer instances. Only after the fleet settles do you measure the new hourly baseline and buy commitments against it.
The order, then:
- Install the CloudWatch agent and opt into Compute Optimizer.
- Wait two weeks for data.
- Rightsize the obvious over-provisioned instances.
- Migrate to Graviton where the checklist passes, or to a newer x86 generation where it doesn’t.
- Watch the new baseline for a few weeks, then commit.
FAQ
How do I rightsize EC2 instances in AWS?
Collect CPU and memory metrics for at least two weeks, with memory coming from the CloudWatch agent. Then use Compute Optimizer or your own peak analysis to find instances whose peaks leave lots of headroom, and step them down one size at a time.
Is Graviton cheaper than Intel on AWS?
Per hour, yes. In us-east-1, m8g.large is $0.08976 against $0.10584 for m8i.large, and AWS says Graviton instances cost up to 20% less than comparable x86 instances. Whether it’s cheaper per unit of work depends on your workload, so load-test it.
Does Compute Optimizer use memory utilization?
Only if you send it. Install the CloudWatch agent and publish mem_used_percent (Linux) or Available MBytes (Windows) with the InstanceId dimension, or ingest memory from a supported observability tool.
Should I buy Savings Plans before or after migrating to Graviton?
After. If you must buy before, choose a Compute Savings Plan, because it keeps covering usage when you change families and architecture.
Can I run Windows on Graviton?
No. Graviton instances run Linux. Windows workloads stay on x86, though they still benefit from rightsizing and newer generations.
Start with the inventory
Take the top ten instance types from the inventory command, put the CloudWatch agent on those instances this week and opt into Compute Optimizer. Two weeks later you’ll have two lists: instances to shrink, and workloads that can move to arm64. Work through the first before the second, and leave the commitment purchase until both have settled, because the hourly baseline you size it against will have moved.
