Home › Decoder › EBS:VolumeUsage.piops and VolumeP-IOPS.piops - io1 volumes, and the cheaper gp3
EBS:VolumeUsage.piops and VolumeP-IOPS.piops - io1 volumes, and the cheaper gp3
EBS:VolumeUsage.piops is the storage charge for io1 volumes ($0.125 per GB-month in US East N. Virginia) and EBS:VolumeP-IOPS.piops is the charge for their provisioned IOPS ($0.065 per IOPS-month); gp3 costs $0.08 per GB-month with 3,000 IOPS included.
In your bill the usage type reads: EBS:VolumeUsage.piops, USE1-EBS:VolumeUsage.piops, EBS:VolumeP-IOPS.piops, USE1-EBS:VolumeP-IOPS.piops
What it costs
| Charge | Price | Unit |
|---|---|---|
| io1 storage (EBS:VolumeUsage.piops) | $0.125 | per GB-month |
| io1 provisioned IOPS (EBS:VolumeP-IOPS.piops) | $0.065 | per IOPS-month |
| gp3 storage, 3,000 IOPS and 125 MiB/s included | $0.08 | per GB-month |
| gp3 provisioned IOPS above 3,000 | $0.005 | per IOPS-month |
US East (N. Virginia); other regions differ. Checked 2026-11-04. Sources: AWS EBS pricing, EBS Provisioned IOPS SSD volumes (docs).
Example with made-up numbers
2,000 GB of io1 with 10,000 provisioned IOPS
Storage: 2,000 GB x $0.125 = $250.00 per month; IOPS: 10,000 x $0.065 = $650.00 per month; total $900.00
The same volumes on gp3: 2,000 x $0.08 = $160.00, plus (10,000 - 3,000) x $0.005 = $35.00, total $195.00
Difference: $705.00 per month, if the workload really peaks at 10,000 IOPS
Typical causes
Provisioned for a peak that never came
io1 IOPS are bought up front, so a number chosen for a launch or load test keeps billing every month.
Databases moved onto io1 by default
Self-managed databases and older RDS-like setups were often put on io1 before gp3 existed or was understood.
IaC modules pinned to io1
Templates with an explicit io1 type recreate it on every deploy.
Volumes bigger than needed
IOPS and size were raised together; on gp3 they are set independently.
Find it in your bill
Have a CUR file queryable in Athena? Paste this and run it:
SELECT date_trunc('month', line_item_usage_start_date) AS month,
line_item_product_code,
line_item_usage_type,
line_item_resource_id,
SUM(line_item_usage_amount) AS usage_amount,
SUM(line_item_unblended_cost) AS cost
FROM your_cur_table
WHERE line_item_usage_type LIKE '%EBS:VolumeUsage.piops'
OR line_item_usage_type LIKE '%EBS:VolumeP-IOPS.piops'
GROUP BY 1, 2, 3, 4
ORDER BY month DESC, cost DESC
LIMIT 50;Columns used: line_item_usage_start_date, line_item_product_code, line_item_usage_type, line_item_resource_id, line_item_usage_amount, line_item_unblended_cost.
No CUR file? Use Cost Explorer
- Open Billing and Cost Management, then Cost Explorer, and set the date range to the last 3 months with Monthly granularity.
- Set Group by to Usage type.
- In the Filters panel choose Usage type and tick the entries ending in EBS:VolumeUsage.piops or EBS:VolumeP-IOPS.piops (the region prefix, such as USE1-, differs by region).
- Read the monthly cost per usage type. A line that grows faster than your traffic is the one to chase with the fix below.
- To see which resource drives it, query the CUR above; resource IDs appear only if your CUR was set up with resource IDs included, otherwise the resource column is empty.
Column names checked against: docs.aws.amazon.com/cur/latest/userguide/Lineitem-columns.html, docs.aws.amazon.com/cur/latest/userguide/cur-ate-run.html.
The fix, step by step
Verify first
Check these before you change anything: the steps below are suggestions, and only you can see your own configuration.
- Read each volume's real peak IOPS and throughput in CloudWatch before choosing the gp3 setting; do not copy the provisioned io1 number blindly.
- gp3 tops out at 16,000 IOPS; a volume provisioned above that (or one that needs io1/io2 durability or multi-attach) must stay.
- Take a snapshot of a production volume before changing it and test one non-production volume first.
- Check infrastructure-as-code: a template that still says io1 will recreate io1 on the next deploy.
In the console
- EC2 console > Volumes; filter Volume type = io1.
- For each volume open Monitoring and note the busiest hour of VolumeReadOps + VolumeWriteOps and throughput over the last 2 weeks.
- Pick one non-production volume > Actions > Modify volume > Volume type gp3; set IOPS to the real peak (3,000 is free, extra IOPS are $0.005 each) and throughput as needed; Modify.
- Wait for the modification to finish optimizing, watch latency for a day, then repeat for the rest.
- Update launch templates and infrastructure-as-code to gp3.
With the CLI
aws ec2 describe-volumes --filters Name=volume-type,Values=io1 --query 'Volumes[].[VolumeId,Size,Iops,State]' --output table
aws ec2 create-snapshot --volume-id vol-EXAMPLE --description 'before gp3 change'
aws ec2 modify-volume --volume-id vol-EXAMPLE --volume-type gp3 --iops 6000
aws ec2 describe-volumes-modifications --volume-ids vol-EXAMPLE
Rollback
Modify the volume back to io1 with the same IOPS once the previous modification has completed (AWS limits how often a volume can be modified); keep the pre-change snapshot for a few days.
Find your io1 lines in your own bill
Drop in your Cost Explorer or CUR CSV. Billtrim reads it in your browser, finds the io1 storage and IOPS lines and shows the monthly dollars.
Paste your export: how to export a Cost Explorer CSV or CUR file in two minutes.
Questions people ask
Can gp3 replace every io1 volume?
No. gp3 supports up to 16,000 IOPS; volumes provisioned above that, or that need multi-attach, stay on io1 or io2.
Does the change cause downtime?
AWS documents that Elastic Volumes lets you change type and performance while the volume is attached. Test on one volume first.
Is IOPS billed even when idle?
Yes, provisioned IOPS are billed per IOPS-month whether or not the volume uses them.
Related
- EBS:VolumeUsage.gp2 - the gp2 volume line, and the cheaper gp3
- EBS:SnapshotUsage - the snapshot storage line, and how to trim it
- TimedStorage-ByteHrs: the S3 Standard storage line
Part of the guide: Reduce your AWS bill as a startup: a checklist for teams with no FinOps person