Skip to content

gp2 vs gp3 cost savings

gp3 is 20% cheaper per GB-month than gp2, and it lets you buy IOPS and throughput separately from capacity. There is no reason for new volumes to be created as gp2, and most existing gp2 volumes should move. The exception is a volume that actually uses more than gp3's included 125 MB/s or 3,000 IOPS: gp3 needs extra provisioned performance to match it, and at some sizes that costs more than staying on gp2 (see below). This guide walks through the price math, the IOPS / throughput differences, and the online migration.

Run the gp2 → gp3 calculator →


The price math

Volume type $/GB-month (us-east-1) Baseline IOPS Baseline throughput
gp2 $0.10 3 IOPS/GB (min 100, burst to 3,000) 250 MB/s max
gp3 $0.08 3,000 IOPS flat 125 MB/s flat

For IOPS, gp3's included 3,000 equal the most a gp2 volume under 1,000 GiB can reach, and gp2 reaches it only in bursts, on credits; its baseline is 3 IOPS per GiB. For throughput, gp2 is slightly ahead up to 170 GiB (128 MB/s against gp3's included 125) and further ahead above that, where it reaches up to 250 MB/s: on burst credits between 171 and 333 GiB, as a baseline from 334 GiB.

The 20% rule of thumb, before any extra IOPS or throughput you provision on gp3: for every 5 TB of gp2 you migrate, you save ~$1,200/year. The math is linear:

Annual savings = provisioned_gb × ($0.10 − $0.08) × 12
                ≈ provisioned_gb × $0.24

For 5,000 GB → 5,000 × $0.24 = $1,200/yr.


Why teams haven't already done this

The migration was straightforward from day one of gp3 (Dec 2020) but it kept getting pushed because:

  1. "It can't be this simple." Teams assume there's a tradeoff. For most volumes there isn't one; the exceptions are the throughput and IOPS cases below.
  2. "What if we need the gp2 burst behaviour?" gp3's included 3,000 IOPS equal gp2's burst ceiling on a volume under 1,000 GiB, and gp3 holds them without burst credits. The bursting argument inverts on small volumes.
  3. "What if it's slow during the migration?" AWS ModifyVolume is online. The IOPS are gradually rebuilt in the background; the workload doesn't see a stop.

How ModifyVolume actually works

When you change a volume type, AWS runs the migration in the background. The volume reports a state: modifying in the EC2 API while it's working, and a state: completed when it's done. Throughout:

  • The workload keeps reading and writing — no remount, no fsfreeze, no signal to the application.
  • Performance during the migration is at the slower of the two configurations. For gp2 → gp3 specifically, the practical effect is: same gp2 perf for a few hours, then gp3 perf.
  • You can monitor progress via the EC2 console "Modifications" tab or aws ec2 describe-volumes-modifications.

The migration is idempotent — re-running it on an already-converted volume returns immediately with optimizing or completed.


When you need more than gp3's baseline

For databases doing >3,000 sustained IOPS or >125 MB/s sustained throughput, gp3 lets you provision extra at marginal cost:

Extra Rate
Extra IOPS (above 3,000, up to 80,000) $0.005/IOPS-month
Extra throughput (above 125 MB/s, up to 2,000 MB/s) $0.04/MB/s-month

Matching gp2 exactly

A gp2 volume gets 128 MB/s up to 170 GiB and up to 250 MB/s above that (on burst credits between 171 and 333 GiB, as a baseline from 334 GiB), and over 1,000 GiB more than 3,000 IOPS. To keep that on gp3, you provision the difference. Our calculator matches the most gp2 can deliver, counting that burst throughput. Matched that way, at list prices in the five regions it prices, gp3 costs more than gp2 at 1–5 GiB and at 171–249 GiB, and saves less than 20% at every other size: about 10% at 500 GiB and 15% at 1,000 GiB in us-east-1. Before paying for matched performance, check what the volume actually uses (step 2 below): a volume that never needs more than gp3's included 125 MB/s and 3,000 IOPS gets the full 20%.

Even with significant extra IOPS provisioned, gp3 typically beats gp2 and io1. The exception is workloads needing more than 80,000 IOPS or 2,000 MB/s — those should be on io2 for the durability + performance guarantees.


Worked example

A startup with 5 TB (5,000 GB) of EBS gp2 volumes on EC2 instances and self-managed databases:

5,000 × $0.10 = $500/mo on gp2
5,000 × $0.08 = $400/mo on gp3
                ────────
Monthly savings = $100/mo
Annual savings  = $1,200/yr (20% cut on the gp2 storage line, with no extra gp3 performance provisioned)

The migration is online (one aws ec2 modify-volume per volume), and volumes under 1,000 GiB gain sustained IOPS at the same time: gp3 includes 3,000, where gp2's baseline is 3 per GiB.

Amazon RDS storage is not part of this saving: RDS prices gp2 and gp3 storage the same, to within $0.001 per GB-month, in the regions our calculators price, and you change it through RDS (aws rds modify-db-instance), not modify-volume.


How to migrate without breaking anything

Step 1: audit your gp2 footprint

aws ec2 describe-volumes \
  --filters 'Name=volume-type,Values=gp2' \
  --query 'Volumes[].{Id:VolumeId,Size:Size,Iops:Iops,Throughput:Throughput,State:State,Attached:Attachments[0].InstanceId}' \
  --output table

Note the volumes with Attached: None — those are unattached and shouldn't be migrated. They should be deleted instead (after a snapshot).

Step 2: identify the volumes that need extra IOPS / throughput

For each volume over 1,000 GiB (IOPS) or over 170 GiB (throughput), check what it actually uses:

aws cloudwatch get-metric-statistics \
  --namespace AWS/EBS \
  --metric-name VolumeReadOps \
  --dimensions Name=VolumeId,Value=vol-... \
  --start-time $(date -u -v-7d '+%Y-%m-%dT%H:%M:%S') \
  --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') \
  --period 300 --statistics Sum

Each Sum is the operations in five minutes; divide it by 300 for IOPS. Repeat with VolumeWriteOps, and with VolumeReadBytes and VolumeWriteBytes for throughput (bytes ÷ 300 ÷ 1,048,576 for MB/s). If the volume sustained more than ~3,000 IOPS or ~125 MB/s for any meaningful window, plan to provision extra gp3 IOPS or throughput to match.

Step 3: roll out in waves

  1. Dev / staging volumes first. Migrate them, watch CloudWatch IOPS + throughput for a week. Volumes you matched, or that never used more than 125 MB/s and 3,000 IOPS, should look identical or better; a volume moved without the extra throughput it used is capped at 125 MB/s.
  2. Then production replicas / standbys. Migrate the read replica before the primary so you can fail over if needed.
  3. Then production primaries. No downtime, but pick a low-traffic window in case the host needs to rebalance after the modification.

Step 4: prevent the regression

Set a tagging policy / Service Control Policy so new volumes default to gp3:

{
  "Statement": [{
    "Effect": "Deny",
    "Action": "ec2:CreateVolume",
    "Resource": "arn:aws:ec2:*:*:volume/*",
    "Condition": {
      "StringEquals": { "ec2:VolumeType": "gp2" }
    }
  }]
}

Otherwise teams will keep creating gp2 by reflex.


Where to go next

Want a verified savings number?

Book a free 30-minute audit and we'll go through your EBS footprint, tag the migration candidates by dollar size, and produce a Terraform PR that does the modify-volume calls in a controlled rollout.

Book a free audit →