awsebscost-optimizationmigration

When gp2 to gp3 actually saves money (and when it doesn't)

gp3 is 20% cheaper than gp2 with better baseline performance. The migration is in-place with no downtime. Here's exactly when it saves money, when it doesn't, and how to migrate safely at scale.

The C3X Team··8 min read

Quick answer

gp3 is 20% cheaper than gp2 at the same size and gives 3,000 baseline IOPS for free (vs gp2's 3 IOPS/GB scaling). For volumes over ~50 GB it's a strict cost win with usually-better performance. The migration is in-place with no downtime. Skip the migration only if your workload needs IOPS scaling unique to gp2's old model (rare) or if you're already paying for gp3 IOPS above 3,000 baseline (then verify the IOPS are actually needed).

AWS launched gp3 in late 2020 as the replacement for gp2. Five years later, most production AWS accounts still have gp2 volumes sitting around, costing 20% more than they need to. This post covers exactly when gp3 saves money, when it doesn't, and how to do the migration safely.

The pricing difference, in dollars

At the same size, gp3 is roughly 20% cheaper than gp2 per GB-month. In us-east-1:

  • gp2: $0.10/GB-month
  • gp3: $0.08/GB-month

For a 1 TB volume that's a $20/month savings, $240/year. Multiply by every volume in your account. Even a moderate-sized AWS account with 50 TB of gp2 across various EC2 instances pays $1,000/month more than necessary by staying on gp2.

The savings get bigger when you factor in IOPS. gp2's IOPS scale at 3 IOPS per GB, with a minimum of 100 IOPS and a maximum of 16,000. A 100 GB gp2 volume gives you 300 IOPS. A 1 TB gp2 gives 3,000 IOPS. To get more IOPS on gp2, you had to grow the volume size, meaning you paid for storage you didn't need.

gp3 includes 3,000 baseline IOPS at any size. A 50 GB gp3 volume has the same 3,000 IOPS as a 1 TB gp3 volume. Volume size and IOPS are decoupled. If you previously over-provisioned gp2 storage to get IOPS, you can downsize gp3 and save further. For the full pricing breakdown and how C3X estimates EBS volumes, see the aws_ebs_volume catalog page.

When gp3 doesn't save money

Two situations where you should pause before migrating:

1. Very small volumes where IOPS aren't useful

For volumes under ~50 GB, the 3,000 baseline IOPS on gp3 are usually overkill. A 20 GB gp2 boot disk with 100 IOPS is fine for most workloads, and a 20 GB gp3 still costs 20% less. But the 20% savings on a $2/month volume is $0.40/month. Don't expect this to move the needle.

Still migrate (the savings stack across volumes), but don't expect any one tiny volume to matter.

2. Volumes already paying for high provisioned IOPS

If you've already migrated to gp3 and are paying for, say, 16,000 provisioned IOPS at $0.005/IOPS-month, that's $65/month on top of storage. If you don't actually need 16,000 IOPS, you're overspending worse than on gp2.

Check CloudWatch's VolumeReadOps and VolumeWriteOps. Sum the two for total IOPS. If your p99 is well below your provisioned amount, reduce the provisioned IOPS.

The migration: ModifyVolume API

AWS supports in-place volume type changes via the ModifyVolume API. The volume stays attached, the instance keeps running. There's no downtime.

One volume at a time (AWS CLI)

aws ec2 modify-volume \
  --volume-id vol-0123456789abcdef0 \
  --volume-type gp3

That's the whole command. After it returns, the volume enters "optimizing" state for a few minutes to a few hours depending on size. Performance is reduced during this period but the volume stays usable.

Many volumes via Terraform

If your volumes are managed in Terraform, change the type attribute and apply. Terraform issues ModifyVolume under the hood.

resource "aws_ebs_volume" "data" {
  availability_zone = "us-east-1a"
  size              = 1000
  type              = "gp3"  # was "gp2"

  tags = {
    Name = "data-volume"
  }
}

For inline EBS block devices on aws_instance:

resource "aws_instance" "api" {
  ami           = "ami-..."
  instance_type = "m6i.large"

  root_block_device {
    volume_type = "gp3"  # was "gp2"
    volume_size = 100
  }

  ebs_block_device {
    device_name = "/dev/sdf"
    volume_type = "gp3"  # was "gp2"
    volume_size = 500
  }
}

Many volumes at once via a script

For volumes outside Terraform's control (snapshots, old volumes, legacy infrastructure), a simple AWS CLI script does bulk migration:

#!/bin/bash
# Migrate all gp2 volumes in current region to gp3

VOLUMES=$(aws ec2 describe-volumes \
  --filters Name=volume-type,Values=gp2 \
  --query 'Volumes[].VolumeId' \
  --output text)

for vol in $VOLUMES; do
  echo "Migrating $vol..."
  aws ec2 modify-volume --volume-id "$vol" --volume-type gp3
done

Run this in non-production first, watch CloudWatch for any IOPS issues, then run in production. The change is reversible with ModifyVolume back to gp2.

How much will you actually save?

The savings depend on three things: total gp2 storage, average volume size, and whether you've been over-provisioning gp2 storage to get IOPS.

Conservative estimate

Take your current gp2 storage in GB. Multiply by $0.02/GB-month (the 20% savings). That's the floor of what you'll save.

An account with 100 TB of gp2 saves at minimum $2,000/month after migration. Larger accounts save proportionally more.

Aggressive estimate

If you've been over-provisioning gp2 size for IOPS (e.g., 1 TB gp2 volumes that only use 200 GB of actual storage but you needed the size for IOPS), gp3 lets you downsize. The same 1 TB workload on gp3 might fit in 200 GB if you weren't using the storage, saving an additional $64/month per volume.

What to do about new volumes

Set gp3 as your default. In Terraform, that means setting the type explicitly:

# In your modules/standard-ec2/main.tf
root_block_device {
  volume_type = "gp3"
  # ...
}

For AWS-managed services that create EBS volumes (EKS managed node groups, ECS-optimized AMIs, etc.), check the launch template's volume type and update to gp3. RDS instances should also use storage_type = "gp3" where supported.

Adjacent optimizations

While you're auditing EBS volumes, two related optimizations have similar mechanics:

  • Delete detached volumes. EBS bills continuously regardless of attachment. Run aws ec2 describe-volumes --filters Name=status,Values=available and delete what isn't needed. Each $40/month volume detached for a year wasted $480.
  • Audit snapshot retention. Snapshots live forever by default. Each is billed for the unique blocks it represents. Tag and lifecycle them. Old snapshots from deleted volumes are common sources of bill bloat.
  • Newer instance generations. EBS performance is partly limited by the EC2 instance's EBS bandwidth. Newer generations (m6i, m7i) give more bandwidth than older (m5) at similar cost. Migrating both volume and instance generation is often a coordinated cost+performance win.

FAQ

Is gp3 always cheaper than gp2?

Almost always, but not infinitely. gp3 is $0.08/GB-month vs gp2's $0.10/GB-month, so 20% cheaper at the same size. gp3 includes 3,000 baseline IOPS for free; gp2's IOPS scale with size at 3 IOPS/GB. The exception is volumes smaller than ~50 GB where gp2's tiny size means the 3,000 free IOPS on gp3 don't actually help, and you're just paying ~20% less for storage.

Can I migrate gp2 to gp3 without downtime?

Yes. AWS supports in-place modification with the ModifyVolume API. The volume type changes from gp2 to gp3 while the volume stays attached and the instance keeps running. There's a brief period where IOPS are reduced during the transition (a few minutes for most volumes), but no downtime for the instance.

When should I provision extra IOPS on gp3?

Only when actual IOPS demand exceeds the 3,000 baseline. Check CloudWatch's VolumeReadOps and VolumeWriteOps metrics. If your peak combined IOPS is consistently below 3,000, the baseline is sufficient and you don't need to pay for more. If it's above 3,000, provision just enough to cover the peak with some headroom; $0.005 per provisioned IOPS-month adds up fast.

What about gp3 throughput?

gp3 includes 125 MB/s of throughput at the baseline rate. For workloads that need more (large-file workloads, video encoding, certain database scans), you can provision up to 1,000 MB/s at $0.04/MB/s-month. Most application workloads don't need more than baseline.

Does gp3 affect performance for my database?

For most databases, no. gp3's 3,000 baseline IOPS handles small-to-medium Postgres, MySQL, MongoDB workloads. For heavy databases, check IOPS utilization first. If gp2 was already at its size-derived IOPS ceiling (e.g., a 100 GB gp2 maxed at 300 IOPS), gp3 with baseline 3,000 IOPS is actually a 10x performance improvement at lower cost.

How long does the migration take?

The ModifyVolume call returns immediately. The actual transition takes minutes to hours depending on volume size. CloudWatch's VolumeAttachmentState and the volume's State field show 'optimizing' during the transition. The instance and volume remain usable throughout.

How to plan and verify the migration

Three steps from triage to done:

  1. Inventory. aws ec2 describe-volumes --filters Name=volume-type,Values=gp2 gives you the full list. Sum the sizes to see total potential savings.
  2. Estimate impact with C3X. Run c3x estimate --path . on your Terraform before changing volume types, then again after. The diff shows exact savings per resource.
  3. Migrate in waves. Start with non-production. Wait 24 hours, watch IOPS metrics, then continue. For production, do a handful of volumes at a time.

For more detail on EBS pricing dimensions and how Terraform configs map to costs, the aws_ebs_volume catalog page has the full reference. For getting started with C3X in general, see the quickstart guide.

Try C3X on your own Terraform

Free and open source. No API key required. One command to install, one command to estimate.