pd-ssd vs pd-balanced: cut GCP disk costs about 40%

Your Google Cloud bill has an SSD line item paying for a performance ceiling you never reach. pd-ssd disks list at about $0.17 per GiB-month. pd-balanced disks give you the same SSD-class latency for about $0.10. That is roughly 40% off the same storage. The one wrinkle is that Google Cloud will not let you flip the type on a live disk the way AWS does, so the migration takes a little more care. Here is the whole thing.
Why pd-balanced is almost always cheaper than pd-ssd
pd-balanced is Google Cloud's modern general-purpose SSD disk. It is the default boot disk type for most machine families now. As of mid-2026, Google Cloud's Persistent Disk pricing lists pd-balanced at about $0.10 per GiB-month and pd-ssd at about $0.17 per GiB-month for the same storage in a US region like us-central1. Move a disk across and you bank roughly 40% before you touch anything else.
Both types sit on SSD, so latency stays in the same class. The thing you trade away is the performance ceiling, and it is a ceiling most disks never come near.
On Google Cloud's published Persistent Disk performance numbers, pd-ssd delivers 30 IOPS per GiB and pd-balanced delivers 6 IOPS per GiB. On throughput, pd-ssd offers about 0.48 MiB/s per GiB against pd-balanced's 0.28 MiB/s per GiB. So a large, busy disk can drive far more IO on pd-ssd than on pd-balanced of the same size.
Here are the two types side by side, priced and sized against a 100 GiB volume.
| Measure (100 GiB volume) | pd-ssd | pd-balanced |
|---|---|---|
| Price per GiB-month | About $0.17 | About $0.10 |
| Cost per month | About $17 | About $10 |
| Read and write IOPS per GiB | 30 | 6 |
| IOPS ceiling | 3,000 | 600 |
| Throughput per GiB | 0.48 MiB/s | 0.28 MiB/s |
| Throughput ceiling | 48 MiB/s | 28 MiB/s |
| Latency class | SSD | SSD |
| Type change on a live disk | No | No |
| Usual fit | Small disks with sustained heavy IO | Boot disks, logs, most app storage |
Those ceilings are what the disk offers. Your instance decides how much of it you actually get. Persistent Disk performance is also capped per instance by machine type and vCPU count, so a small VM can sit under the disk's ceiling however much capacity you provision. Check the per-instance limit for your machine type before you size a disk to buy IOPS.
Now look at what a normal disk actually does. At 6 IOPS per GiB, a 100 GiB volume on pd-balanced can reach 600 IOPS. A boot disk for an app server rarely sustains a fraction of that. For boot disks, log volumes, and most application storage, the pd-balanced ceiling sits far above the real workload, and the 40% is close to free.
The trap: disk type is immutable
Here is the part that catches everyone who comes over from AWS.
On AWS you migrate gp2 to gp3 with a single modify-volume call, and the disk converts underneath a running instance with no downtime. That gp2 to gp3 migration is a live change with the instance still serving traffic. Google Cloud works differently. A Persistent Disk's type is set when the disk is created and cannot be changed in place, so converting pd-ssd to pd-balanced means creating a new disk from a snapshot and swapping it in.
That swap is the whole job. You snapshot the pd-ssd disk, create a fresh pd-balanced disk from that snapshot, then detach the old disk and attach the new one. What that costs you in downtime depends on which disk you are moving. A data disk detaches and reattaches on a running instance, so the VM keeps serving and only the mount point goes away. A boot disk attaches only to a stopped instance, so that is the one that buys you a maintenance window.
There is a second thing to watch during the swap. For a while you pay for three things at once: the original pd-ssd disk, the new pd-balanced disk, and the snapshot in between. The saving only lands once you have confirmed the new disk is healthy and deleted the original. Until then the meter runs a little higher, so avoid leaving a half-finished migration parked for a week.
Find your pd-ssd candidates by hand
Start by listing every pd-ssd disk in the project. The gcloud CLI filters on disk type directly.
gcloud compute disks list \
--filter="type:pd-ssd" \
--format="table(name, zone, sizeGb, users.basename())"
The users column tells you whether the disk is attached to an instance. A row with a user is a disk doing work and worth migrating. An empty users column means the disk is unattached, and an unattached disk is a different conversation. You are usually better off deleting it than converting it, because you are paying for storage that serves nothing.
Filter straight to the attached disks that are costing you:
gcloud compute disks list \
--filter="type:pd-ssd AND users:*" \
--format="table(name, zone, sizeGb, users.basename())"
That gives you the migration pile. One case deserves a second look before you move it: a small disk with heavy IO. pd-balanced scales its IOPS ceiling with size at 6 IOPS per GiB, so a 50 GiB volume caps around 300 IOPS. If a small database disk has been leaning on pd-ssd's higher ceiling, pull its real numbers before you touch it. The disk metrics compute.googleapis.com/instance/disk/read_ops_count and write_ops_count in Cloud Monitoring count operations per sample period, not IOPS, so align them to a per-second rate (the rate aligner) and read the peak. Compare that peak against 6 IOPS per GiB at its current size and decide.
Persistent Disks are zonal or regional, and gcloud compute disks list sweeps every zone in the project at once. Run it in every project you own, because a forgotten pd-ssd disk in a project nobody opens still bills every hour.
for proj in $(gcloud projects list --format="value(projectId)"); do
echo "== $proj =="
gcloud compute disks list --project="$proj" \
--filter="type:pd-ssd AND users:*" \
--format="table(name, zone, sizeGb)"
done
Build the replacement while the VM keeps running
Once you have the list, every disk follows the same two steps to build its replacement. Snapshot the source, then create the pd-balanced disk from that snapshot. Neither step wants the instance stopped, because Google Cloud snapshots a disk that is still attached to a running VM.
# 1. Snapshot the source pd-ssd disk. The instance keeps serving.
gcloud compute disks snapshot my-disk \
--zone=us-central1-a \
--snapshot-names=my-disk-prebalanced-us-central1-a
# 2. Create a new pd-balanced disk from that snapshot.
gcloud compute disks create my-disk-balanced \
--zone=us-central1-a \
--type=pd-balanced \
--source-snapshot=my-disk-prebalanced-us-central1-a
Snapshot names live in one project-wide namespace, not a per-zone one, so qualify the name with the disk and its zone when you migrate more than one. Two disks both called my-disk in different zones would otherwise collide on a bare my-disk-prebalanced and hand you the wrong rollback target.
A live snapshot captures whatever was in flight when it ran. That is fine for a log volume or a disk of static assets. When the disk backs a database or anything else keeping its own on-disk state, pause the writers first and resume them once the snapshot reaches UPLOADING, which is the point where Google Cloud has what it needs and no longer depends on the source.
Swap a data disk without stopping the VM
A data disk detaches and reattaches on a running instance, so the workload never goes down for the swap. Unmount the filesystem inside the guest first. Pulling a disk out from under open writes risks incomplete IO and a volume you have to fsck later.
# In the guest: stop the writers, then unmount the old disk.
sudo umount /mnt/disks/my-disk
gcloud compute instances detach-disk my-instance \
--disk=my-disk --zone=us-central1-a
gcloud compute instances attach-disk my-instance \
--disk=my-disk-balanced --device-name=my-disk --zone=us-central1-a
Reuse the original --device-name on the attach. Compute Engine exposes the disk to the guest at /dev/disk/by-id/google-<device-name>, so keeping the name keeps that path stable. The disk you built from the snapshot also carries the same filesystem UUID as the original, which means an /etc/fstab line keyed on UUID= mounts the replacement with no edit at all.
# In the guest: mount the replacement and check the data is there.
sudo mount /mnt/disks/my-disk
A boot disk is the one that needs a window
Google Cloud attaches and detaches a boot disk only on a stopped instance, so this swap costs you the maintenance window the data disk did not. Add --boot on the attach so the instance knows which disk to boot from.
gcloud compute instances stop my-instance --zone=us-central1-a
gcloud compute instances detach-disk my-instance \
--disk=my-disk --zone=us-central1-a
gcloud compute instances attach-disk my-instance \
--disk=my-disk-balanced --boot --zone=us-central1-a
gcloud compute instances start my-instance --zone=us-central1-a
Roll back, or delete what you replaced
Confirm the workload is happy before you clean anything up. If something looks wrong, rollback is quick, because the original pd-ssd disk is still sitting there untouched. Detach the pd-balanced disk and reattach the original. A data disk rolls back on the running instance the same way it swapped.
sudo umount /mnt/disks/my-disk
gcloud compute instances detach-disk my-instance --disk=my-disk-balanced --zone=us-central1-a
gcloud compute instances attach-disk my-instance --disk=my-disk --device-name=my-disk --zone=us-central1-a
sudo mount /mnt/disks/my-disk
A boot disk rolls back through the stop and start, because that is the only way it attaches.
gcloud compute instances stop my-instance --zone=us-central1-a
gcloud compute instances detach-disk my-instance --disk=my-disk-balanced --zone=us-central1-a
gcloud compute instances attach-disk my-instance --disk=my-disk --boot --zone=us-central1-a
gcloud compute instances start my-instance --zone=us-central1-a
Only after the new disk has proven itself do you delete the original disk and the snapshot. This is the step that actually turns the swap into a lower bill.
gcloud compute disks delete my-disk --zone=us-central1-a --quiet
gcloud compute snapshots delete my-disk-prebalanced-us-central1-a --quiet
Regional disks need their own flags
Every command above assumes a zonal disk. Run them against a regional pd-ssd disk and you quietly throw away the replication you have been paying extra for.
Regional Persistent Disk keeps a synchronous copy of the disk in a second zone. That is the whole reason for the higher per-GiB rate, and it does not survive a snapshot restore by itself. gcloud compute disks create accepts either --zone or --region, never both, so the create step above produces a zonal disk by definition. You end up with a working disk, a smaller bill, and one copy of your data. Nothing looks wrong until a zone fails, which is the event the disk was there to survive.
Regional disks are easy to miss in the discovery step too. A regional disk carries a region field where a zonal disk carries zone, so it still shows up in the earlier listing, just with an empty zone column. Ask for them on their own instead.
gcloud compute disks list \
--filter="type:pd-ssd AND region:*" \
--format="table(name, region.basename(), sizeGb, users.basename())"
Whatever comes back takes --region in place of --zone at every step. The create also wants --replica-zones, which takes exactly two zones, both inside the region you named.
gcloud compute disks snapshot my-disk \
--region=us-central1 \
--snapshot-names=my-disk-prebalanced-us-central1
gcloud compute disks create my-disk-balanced \
--region=us-central1 \
--replica-zones=us-central1-a,us-central1-b \
--type=pd-balanced \
--source-snapshot=my-disk-prebalanced-us-central1
Both attach-disk and detach-disk default to --disk-scope=zonal, so the swap commands from the last section go looking for a zonal disk under that name and fail on a regional one. Name the scope on each.
gcloud compute instances detach-disk my-instance \
--disk=my-disk --disk-scope=regional --zone=us-central1-a
gcloud compute instances attach-disk my-instance \
--disk=my-disk-balanced --disk-scope=regional \
--device-name=my-disk --zone=us-central1-a
Confirm the replacement really is regional before you delete the original, because a zonal disk cannot be promoted to regional in place. Getting it wrong means snapshotting again and rebuilding with the flags above. Regional pd-balanced and pd-ssd both start at a 10 GiB minimum, so size is not what trips you here.
The cleanup from the last section needs the same care. gcloud compute disks delete goes looking for a zonal disk without --region, and the snapshot you are removing carries the region-qualified name you gave it above.
gcloud compute disks delete my-disk --region=us-central1 --quiet
gcloud compute snapshots delete my-disk-prebalanced-us-central1 --quiet
When Hyperdisk Balanced is the better target
pd-balanced is not the only newer disk type on the menu. Google Cloud has a second general-purpose SSD family called Hyperdisk Balanced, and for most workloads Google now points you at it first. It is worth knowing where it fits before you move everything to pd-balanced out of habit.
The difference that matters for cost is how performance gets priced. On Persistent Disk, IOPS and throughput are welded to size. pd-balanced gives you 6 IOPS per GiB, so the only way to raise the ceiling is to pay for capacity you may not need. Hyperdisk Balanced splits the two apart. You pay for capacity per GiB, and you provision IOPS and throughput as separate dials. The first 3,000 IOPS and 140 MiB/s on every volume come free, and you pay only for performance above that baseline.
That changes the "small disk with heavy IO" case from earlier. A 50 GiB pd-balanced disk caps around 300 IOPS, which is why a busy little database disk gets stranded on pd-ssd. The same 50 GiB on Hyperdisk Balanced starts with 3,000 IOPS for free, without inflating the capacity to get there. For that specific disk, Hyperdisk Balanced is usually the smarter destination than staying on pd-ssd or padding the disk to buy headroom.
Check one thing before you plan the move, because it decides whether Hyperdisk is on the table at all. Hyperdisk only attaches to newer machine series like C3, C4, N4, and the M families. The older series where most legacy pd-ssd disks actually live, N1, N2, N2D, E2, and C2, will not take a Hyperdisk disk. So if your instance runs on one of those, the swap above breaks: gcloud compute instances attach-disk my-instance --disk=my-disk-balanced fails against a hyperdisk-balanced disk, and no extra flag gets you past it. Getting to Hyperdisk then means migrating the instance to a supported machine type first, which is a bigger change than a disk swap and a separate decision to weigh.
When the instance already runs on a Hyperdisk-capable series, migrate exactly as above and create the replacement with --type=hyperdisk-balanced in place of --type=pd-balanced. The snapshot and swap are the same steps. When it does not, Persistent Disk is your lane and the pd-balanced migration is the win you can take today without touching the machine type.
For ordinary boot disks, log volumes, and app storage that live well inside the free performance baseline, the pd-balanced migration above is the simplest path to the saving, and the extra dials on Hyperdisk buy you nothing. Reach for Hyperdisk Balanced when a disk needs more IOPS than 6 per GiB will give it at a size you actually want to pay for.
A quick decision tree
graph TD
A[pd-ssd disk] --> B{Attached to an instance?}
B -- No --> C[Delete it: you pay for storage serving nothing]
B -- Yes --> D{Small disk with heavy IO?}
D -- No --> E[Snapshot, recreate as pd-balanced, swap it in]
D -- Yes --> F[Check Cloud Monitoring IOPS against 6 per GiB]
F --> G{Real IOPS under the pd-balanced ceiling?}
G -- Yes --> E
G -- No --> H[Move to Hyperdisk Balanced, or keep pd-ssd if the machine type lacks Hyperdisk]
E --> I{Boot disk?}
I -- No --> J[Unmount, detach and attach on the running VM]
I -- Yes --> K[Stop the VM, swap the boot disk, start it again]
Related reading
Buying a ceiling the workload never reaches is the same bill in a few different shapes.
- Bedrock provisioned throughput bills when idle is the inference version. A model unit sells you capacity rather than usage, so it charges the same hourly rate whether you push tokens through it or leave it alone.
- Cut Kubernetes costs by right-sizing pod requests is the compute version. A pod's requests reserve CPU and memory on a node whether the container touches them or not, and that gap is the one pd-ssd charges you for at 30 IOPS per GiB.
- Azure VM stopped vs deallocated: stopped still bills is where a disk bill outlives its machine. Deallocating a VM stops the compute meter and leaves every attached disk billing, so the type you picked keeps charging after the instance goes quiet.
- Find unused Postgres indexes and stop paying to keep them is the layer above the disk. An index no query plan has touched still occupies the volume you just migrated, and it taxes every write that keeps it current.
- Is cloud spend COGS or OpEx? Make it a margin KPI is what makes the migration worth queueing at all. Storage sits in cost of revenue, so 40% off an SSD line item reads as gross margin on the finance side.
Common questions
How much cheaper is pd-balanced than pd-ssd? pd-balanced lists at about $0.10 per GiB-month against pd-ssd's roughly $0.17 for the same SSD-class storage in a US region like us-central1, so moving a disk across saves close to 40% on that line item. The saving only lands once you delete the original pd-ssd disk and the migration snapshot, so finish the cleanup before you count it.
Can I change a disk from pd-ssd to pd-balanced in place?
No. A Persistent Disk's type is fixed when the disk is created, unlike AWS where a single modify-volume call converts gp2 to gp3 on a live instance. Converting on Google Cloud means snapshotting the pd-ssd disk, creating a fresh pd-balanced disk from that snapshot, and swapping it in. Only a boot disk makes you stop the VM to do it.
Do I have to stop the VM to migrate a pd-ssd disk? Only for a boot disk. Google Cloud attaches and detaches a boot disk on a stopped instance alone, so that swap costs a maintenance window. A data disk detaches and reattaches while the instance runs, and the snapshot and the disk create both happen against a running VM, so the outage is the unmount and remount rather than the whole machine. Unmount inside the guest before you detach, because Google recommends it to avoid incomplete IO.
Is pd-balanced slower than pd-ssd? Both types sit on SSD, so read and write latency stays in the same class. What you trade away is the performance ceiling: pd-ssd delivers 30 IOPS per GiB and pd-balanced delivers 6, so a 100 GiB pd-balanced disk caps around 600 IOPS. Boot disks, log volumes, and most application storage never come near that ceiling, which is why the 40% is close to free for them.
Should I migrate to pd-balanced or Hyperdisk Balanced? Reach for Hyperdisk Balanced when a small disk needs more IOPS than 6 per GiB gives it at a size you want to pay for, because Hyperdisk provisions IOPS separately from capacity and includes 3,000 IOPS free on every volume. The catch is machine support: Hyperdisk only attaches to newer series like C3, C4, N4, and the M families, so an older N1, N2, N2D, E2, or C2 instance has to move machine type first. For ordinary disks inside the free performance baseline, plain pd-balanced is the simpler win.
Do regional persistent disks migrate the same way?
The steps are the same and the flags are not. A regional disk takes --region everywhere a zonal disk takes --zone, the create needs --replica-zones with exactly two zones inside that region, and both attach-disk and detach-disk need --disk-scope=regional because they default to zonal. Miss those and you build a zonal pd-balanced disk that works fine and no longer keeps a copy in a second zone.
Does a boot disk migrate differently from a data disk?
The two build steps are identical: snapshot the source, then create the pd-balanced disk from that snapshot. The swap is where they part. A data disk unmounts, detaches and reattaches by device name with --device-name on a running instance. A boot disk needs the VM stopped and --boot on the attach so the instance knows which disk to boot from.
How OhChimp approaches this
Because the type is immutable, each disk costs you a snapshot, a swap, and, for boot disks, a window you only want to spend once. That means choosing the right target and the right IOPS before you touch the disk, disk by disk, across every project and zone.
OhChimp scans your Google Cloud projects, finds the attached pd-ssd disks, and writes a reviewable plan for each one: snapshot, create the pd-balanced disk, swap it in, with the exact rollback if anything looks off. The IOPS question is answered per disk, so you read a recommendation instead of redoing the analysis. You click apply, nothing changes until you do, and the saving is then checked against your real Google Cloud bill.
See how the Google Cloud connection works on the Google Cloud integration page.