Storage — 10 min read
io1 vs io2 vs gp3: is provisioned IOPS worth the money?
Every other volume type bills on gigabytes. These bill again for each IOPS you reserved, whether anything uses it or not.
Every other EBS volume type bills on one thing: how many gigabytes you asked
for. io1 and io2 bill on two. You pay for the storage, and then you pay
again, separately, for each IOPS you reserved — whether or not anything ever
issues that many requests.
That second meter is the whole guide. It is why provisioned-IOPS volumes turn up
as one of the largest single line items inside EC2 — Other, and why they are
so often the easiest expensive thing to fix in an AWS account.
The second meter
On gp3 you get 3,000 IOPS and 125 MB/s included with every volume, at no additional charge, no matter how small it is. You only pay extra above that, and the extra is around half a cent per IOPS per month.
On io1 and io2 there is no included allowance. Every single IOPS is billed, from the first one, at roughly thirteen times the gp3 rate. Reserve 10,000 IOPS and you are buying 10,000 IOPS every hour of every day, at rest, at idle, over a holiday weekend, forever.
The proportions are the point. On a 500 GB volume at 10,000 IOPS, storage is about 9% of the io1 bill. Arguing about gigabytes on a provisioned-IOPS volume is arguing about the wrong number.
What the same volume costs on each type
Move the sliders to your own volume. The comparison is like-for-like: same size, same guaranteed IOPS, four ways of buying it.
Identical to io1 below 32,000 IOPS — but with far better durability at the same price, so there's no reason to pick io1.
Delivers only 1,500 baseline IOPS at this size — it cannot do the job.
That is one volume. The decision that put it there — provision generously now, revisit once things settle down — is rarely made only once, and the revisit has no owner.
us-east-1 list prices at time of writing — storage and IOPS only, excluding snapshots and transfer. AWS changes pricing; check current rates for your region before acting on a number.
Two things usually surprise people here. The first is how flat gp3 stays — the included 3,000 IOPS covers a lot of ground, and the charge above it is small enough that gp3 remains cheaper than io1 right up to its ceiling. The second is how early gp2 becomes impossible: gp2 grants 3 IOPS per gigabyte, so a 500 GB volume tops out around 1,500 baseline IOPS. Teams that "saved money by staying on gp2" often quietly weren't getting the performance they assumed.
The three types, honestly
gp3 is the general-purpose SSD and the correct default. 3,000 IOPS and 125 MB/s included, provisionable up to 16,000 IOPS and 1,000 MB/s, with a hard ratio limit of 500 IOPS per provisioned gigabyte. That ceiling is the only reason most workloads ever need to look further.
io1 is the older provisioned-IOPS type. Up to 64,000 IOPS on Nitro instances, a 50:1 IOPS-to-GB ratio limit, and a durability figure of 99.8–99.9%. There is essentially no reason to choose io1 for something new today.
io2 supersedes it at the same per-GB storage price: 99.999% durability (two orders of magnitude better), a 500:1 ratio limit, and — the part that matters most on a bill — tiered IOPS pricing. The rate drops above 32,000 IOPS and again above 64,000. io1 charges one flat rate all the way up. At high IOPS counts io2 is not just better, it is cheaper, which is why "io1 vs io2" is rarely a real decision.
io2 Block Express is io2 on newer infrastructure, reaching 256,000 IOPS, 4,000 MB/s and 64 TiB on supported instance families. If you genuinely need numbers like those, you are not choosing between these types by reading a guide — you are choosing by benchmark.
Decide
Does this volume actually need provisioned IOPS?
How to check what you're actually using
This is the part that decides it, and almost nobody does it, because the number isn't shown anywhere you'd naturally look. The EC2 console tells you what you provisioned. It does not tell you what you consumed.
In CloudWatch, for each volume, pull VolumeReadOps and VolumeWriteOps. These
are counts per period, not rates — the most common mistake is reading them
as IOPS directly and concluding the volume is idle. Convert:
IOPS = (VolumeReadOps + VolumeWriteOps) / period_seconds
For a 5-minute period, divide the sum by 300. Take the maximum over 30 days, not the average — an average across a month will make almost any volume look unused and will talk you into an under-provision you'll regret at month-end close or during a nightly batch.
Then compare that peak against what you're paying for:
- Peak under 3,000 — gp3 covers it with the included allowance. Nothing extra to provision, and the saving is the entire IOPS charge.
- Peak between 3,000 and 16,000 — gp3 still covers it. Provision the delta and check the 500:1 ratio holds for the volume size.
- Peak consistently above 16,000 — io2, and you were right.
Do this before touching anything, and add ~20% headroom to the peak rather than provisioning exactly to it.
Why over-provisioning here is the normal outcome
It's worth understanding the mechanism, because it tells you where else to look.
The number gets chosen at the worst possible moment — during a migration or a launch, before anyone has production telemetry, when the cost of being slow is a visible incident and the cost of being generous is an invisible line in a bill nobody reads. Every incentive points the same way. So the number goes in high, with an honest intention to revisit it once things settle.
Revisiting it requires someone to notice a charge that appears inside
EC2 — Other rather than under a service name, then correlate it back to a
decision made months earlier by someone who may have left. That review has no
owner and no trigger. It doesn't happen — not through negligence, but because
nothing in the system ever asks for it.
The same shape explains oversized instances, forgotten Multi-AZ on staging databases, and 35-day backup retention nobody chose. Provisioned IOPS is just the most expensive instance of it per unit of neglect.
Making the change
The migration is an online operation. You modify the volume in place — no snapshot, no detach, no instance restart:
aws ec2 modify-volume --volume-id vol-xxxx --volume-type gp3 --iops 5000
Three things worth knowing before you run it across a fleet:
- There's a cooldown. After modifying a volume you must wait roughly six hours before modifying it again. Get the target IOPS right the first time rather than tuning down in steps.
- The volume stays usable throughout, though it passes through an
optimizingstate where performance is somewhere between the old and new configuration. Do it outside your peak window even though you technically don't have to. - Filesystem size is untouched. You're changing the volume's type and performance, not its capacity, so there's nothing to resize inside the guest.
Where this sits in the wider bill
Provisioned IOPS is one line inside EC2 — Other, the Cost Explorer bucket that
also hides NAT Gateway, inter-AZ transfer, and every EBS volume you own. Split
that bucket by usage type and you'll see which of them you're actually dealing
with — it takes about a minute and it is the single most useful minute in Cost
Explorer.
- EBS volume types explained — all six types, and which one a given workload should be on.
- gp2 vs gp3 — the other migration worth doing, plus unattached volumes and snapshots.
- Why is my RDS bill so high? — RDS provisions IOPS too, and the same reasoning applies with a different console.
If your storage line looks larger than it should and you'd rather not spend the
afternoon in CloudWatch, the free teardown reads a Cost Explorer
export in your browser and tells you how much of the bill is sitting in
EC2 — Other. Nothing is uploaded and there's no signup.
Check your own account
Does this apply to you? Find out in two seconds.
Drop a Cost Explorer export into the free teardown. It reads your service mix and tells you which of these patterns show up in your bill — in your browser, with no upload and no email.