How To

GCP Cost Optimization: 7 Places Your Google Cloud Bill Is Leaking

Google Cloud already knows where you're wasting money. Nobody reads the findings. Seven places GCP bills leak, each with a gcloud command or console path, plus a worked example of tracing an unexplained Compute Engine increase in the BigQuery billing export.

12 min read
Cover Image for GCP Cost Optimization: 7 Places Your Google Cloud Bill Is Leaking

GCP Cost Optimization: 7 Places Your Google Cloud Bill Is Leaking

Google Cloud has a strange property among the big three: the platform already knows where you're wasting money. Active Assist generates idle-VM findings, machine-type recommendations and commitment purchase suggestions for every project you run, and in most organizations nobody reads them. The findings sit on a console page no one visits, quietly refreshing while the bill compounds.

So GCP cost optimization is rarely a detection problem in the pure sense. It's a looking problem. On bills between $10K and $500K a month, the waste concentrates in the same seven places, and for most of them Google will hand you the evidence if you ask. This guide asks. For each leak: what it is, why it forms, one real gcloud command or exact console path to find it, and the range it typically costs. Budget about an hour to run everything.

This is part one of a three-part series. Part two is a DIY GCP cost audit using only native tools. Part three covers running the same checks continuously with an AI agent. If you run AWS alongside GCP, the AWS version of this list follows the same logic with different tooling.

01Leftovers

Orphans

Unattached persistent disks, stale snapshots, reserved IPs and forwarding rules serving nobody.

02Sizing

Oversized

Compute Engine and Cloud SQL sized for a launch-day guess, and non-prod projects running all week.

03Pricing and design

Overpaid

Stable compute at on-demand rates, and traffic crossing zones or leaving through Cloud NAT.

The seven leaks in this guide, grouped by why they bill. Each comes with a command or console path you can run today.

Why the monthly review breaks on Google Cloud

Most teams already "do cost reviews". On GCP they miss the same things for structural reasons:

  • Waste is scattered across projects. GCP makes projects cheap, so every team, environment and experiment gets one. Every gcloud command in this guide is scoped to one project, and a review that covers the top five never sees the other thirty-five.
  • Recommendations are perishable. Recommender findings refresh as usage changes. A rightsizing suggestion built on the last 8 days and acted on three weeks later may describe a workload that no longer exists.
  • The bill is grouped by service, waste is grouped by resource. "Compute Engine up 12%" doesn't say which nine instances, or whether it's usage or rate.
  • Discounts drift on their own. A lapsed commitment changes nothing about your infrastructure except the unit rate, so no alert fires.

Start here: one unexplained increase, worked through

Say Compute Engine net cost rose 22% last month while traffic grew 5%. The numbers are illustrative. Name the possible readings first, because each predicts something different in the billing data:

01Reading 1

Real growth

A new workload or a launch. Predicts new resources with owners and usage that tracks traffic.

02Reading 2

Waste

Things nobody uses. Predicts SKUs rising that have nothing to do with traffic.

03Reading 3

Discount drift

The same usage at a worse rate. Predicts gross cost flat-ish while net cost climbs.

Three readings of an unexplained increase. The BigQuery billing export separates them in about twenty minutes.

Step 1: which SKUs moved, and gross or net? With the BigQuery billing export enabled, one query compares two invoice months by SKU, before and after credits:

SELECT
  invoice.month AS month,
  sku.description AS sku,
  ROUND(SUM(cost), 2) AS gross,
  ROUND(SUM(cost) + SUM(IFNULL((SELECT SUM(c.amount) FROM UNNEST(credits) c), 0)), 2) AS net
FROM `project.dataset.gcp_billing_export_resource_v1_XXXXXX`
WHERE service.description = 'Compute Engine'
  AND invoice.month IN ('202608', '202609')
GROUP BY month, sku
ORDER BY sku, month;

N2 core and RAM SKUs are up only 4% gross but 31% net. Credits shrank. That's reading 3.

Step 2: what happened to the discount? List commitments and their end dates:

gcloud compute commitments list \
  --format="table(name,region,plan,status,endTimestamp)"

A one-year N2 commitment in us-central1 shows EXPIRED, end date in late August. The baseline it covered went back to on-demand rates (#3 below).

Step 3: what's the rest? The remaining increase sits in Storage PD Capacity and SSD backed PD Capacity, which don't track traffic. A disk sweep finds 31 unattached disks from a migration finished in July (#1 below). Reading 2.

Conclusion. No new product explains the 22%. Most of it is an expired commitment, the rest is orphaned disks. Twenty minutes in the billing export turned "nobody can explain it" into two tickets with owners.


1. Orphaned persistent disks and snapshots

The leak. Deleting a Compute Engine VM doesn't delete its persistent disks unless auto-delete was enabled, and for anything beyond the boot disk it almost never is. The disk detaches, keeps its full provisioned billing rate (roughly $0.04/GB-month for standard PD, around $0.17 for SSD), and serves nothing, forever. Snapshots pile up in parallel: migration backups, pre-upgrade safety copies, the output of a scheduled snapshot policy whose cleanup half was never written.

How it forms. Individual disks look cheap and deletion feels irreversible, so the cleanup ticket never wins a sprint. Meanwhile a 500 GB SSD orphan bills about $85 a month for holding data nothing reads.

Find it. Unattached disks are the ones with no users:

gcloud compute disks list \
  --filter="-users:*" \
  --format="table(name,zone,sizeGb,type,lastAttachTimestamp)"

For snapshots, sort by age and compare against your stated retention policy (if you don't have one, that's the first finding):

gcloud compute snapshots list \
  --sort-by=creationTimestamp \
  --format="table(name,diskSizeGb,storageBytes,creationTimestamp)"

Google also surfaces these through the idle resource recommenders. Snapshot a disk before deleting it if you're nervous. The snapshot costs a fraction of the provisioned disk.

What it's worth. Usually 1 to 3% of the bill. The smallest number on this list, but the fastest to collect: by definition, nothing depends on an unattached disk.

2. Idle or oversized instances: the GCP rightsizing gap

The leak. VMs whose CPU has averaged single digits for a month. Some are abandoned experiments that should be deleted outright. Most are honest workloads on the wrong machine type: an n2-standard-16 doing an n2-standard-4 job because someone padded the estimate at launch and nobody has touched it since.

How it forms. Sizing happens once, under uncertainty, with a safety margin. Revisiting it means a restart, a change window and an owner, three things idle VMs conveniently lack.

Find it. This is where GCP rightsizing gets easier than on other clouds: the machine type recommender has already analyzed 8 days of usage for every VM and drafted the resize. Pull its findings per zone:

gcloud recommender recommendations list \
  --project=PROJECT_ID \
  --recommender=google.compute.instance.MachineTypeRecommender \
  --location=us-central1-a

Swap in google.compute.instance.IdleResourceRecommender to catch VMs Google thinks should simply stop existing. Sanity-check memory-bound workloads before applying: the recommender sees CPU well, but memory only where the Ops Agent runs.

What it's worth. Typically 10 to 20% of Compute Engine spend. Each one-step machine-type reduction roughly halves that instance's cost.

3. Committed use discounts: coverage gaps and the SUD ceiling

The leak. Every hour of stable baseline you run at on-demand rates is a discount you declined. Teams often believe sustained use discounts already handle this. They don't. SUDs apply automatically to eligible machine types the longer they run in a month, but they cap out (up to 30% on N1, up to 20% on N2 and C2 for a full month) and never touch E2 or Tau instances at all. Committed use discounts go deeper: roughly 37% off for a one-year resource commitment and up to 55% for three years on most machine families, in exchange for committing to a baseline you were going to run anyway.

How it forms. Two failure modes. Teams that never buy CUDs because commitment feels like a bet nobody owns. And teams that bought them once, let them expire, and silently reverted to list price, which is exactly the walkthrough above.

Find it. List commitments and check end dates:

gcloud compute commitments list \
  --format="table(name,region,plan,status,endTimestamp)"

Then open Billing → Cost optimization → Committed use discount analysis in the console, which charts coverage of eligible spend over time. Healthy coverage of a stable baseline sits around 60 to 80%. Below that you're paying a voluntary premium. Above it you risk paying for commitment you can't use when load shifts.

What it's worth. Frequently the single biggest lever here: 10 to 25% of total compute spend for accounts that never systematized commitments.

4. Unused static IPs and idle load balancers

The leak. A reserved static external IP attached to nothing bills roughly $7 to $8 a month for the privilege. A load balancer's forwarding rules bill hourly (around $18 a month for the first five) whether or not a single request arrives. Both get created per experiment, per migration, per "temporary" endpoint, and outlive all three.

How it forms. IPs get reserved "so the address doesn't change" for services deleted a year ago. Load balancers survive because nobody is sure what still points at them, and nobody wants to find out in production.

Find it. Reserved but unattached addresses:

gcloud compute addresses list \
  --filter="status=RESERVED" \
  --format="table(name,region,address,addressType)"

For load balancers, list the forwarding rules with gcloud compute forwarding-rules list, then check each one's request count in Monitoring → Metrics Explorer (the loadbalancing.googleapis.com/https/request_count metric for HTTP(S) load balancers). Two weeks of near-zero requests means the load balancer is decorative. Releasing an unused IP is instant and carries no risk. Pricing details are in the VPC network pricing docs.

What it's worth. Small in absolute terms, usually $50 to $500 a month, but zero-risk to reclaim and a reliable proxy for how much other hygiene debt the project carries.

5. Over-provisioned Cloud SQL instances

The leak. A db-custom-8-32768 averaging 6% CPU with a dozen connections, sized for launch-day traffic that never came. Databases attract the worst over-provisioning on any cloud because nobody resizes a production instance that's working, and with high availability enabled, every wasted vCPU bills twice.

How it forms. The person who sized it has left. Resizing means a brief failover. "It's the database" ends the conversation.

Find it. Open Monitoring → Metrics Explorer and chart cloudsql.googleapis.com/database/cpu/utilization over 30 days for each production instance. Then chart database/memory/utilization too, because CPU alone flatters memory-bound databases. Sustained CPU under 20% with memory under 60% is a strong downsize candidate. Google also ships a dedicated over-provisioned Cloud SQL recommender that runs this analysis for you:

gcloud recommender recommendations list \
  --project=PROJECT_ID \
  --recommender=google.cloudsql.instance.OverprovisionedRecommender \
  --location=us-central1

What it's worth. Cloud SQL is often 15 to 30% of a SaaS bill, and teams that audit it typically find 20 to 40% savings within the database line itself.

6. Network egress and inter-zone traffic

The leak. Data transfer is the line item nobody designed for. Inter-zone traffic bills about $0.01/GB in each direction: trivial per gigabyte, four figures a month once chatty microservices sit in different zones. Internet egress starts around $0.085 to $0.12/GB depending on destination, and Cloud NAT adds a per-GB processing charge on top (network pricing).

How it forms. No infrastructure plan says how many terabytes two services will exchange across zones each month. Regional GKE clusters spread pods across three zones by design, which is excellent for availability and quietly expensive for chatty east-west traffic.

Find it. In Billing → Reports, set Group by → SKU and search the results for "egress", "data transfer" and "inter zone". The pattern to hunt is a steadily growing inter-zone SKU under networking. That's architecture, not usage, and it won't fix itself. The classic remediations: co-locate chatty service pairs in one zone where the availability math allows it, enable Private Google Access so traffic to Google APIs skips Cloud NAT, and put a CDN in front of anything serving large objects to the internet.

What it's worth. Highly variable: near zero for monoliths, 5 to 15% of the bill for distributed architectures.

7. Non-production environments running 24/7

The leak. Dev, staging, QA and demo projects billing 168 hours a week to support a work week that needs roughly 60 of them. Everything running outside those hours (VMs, GKE nodes, Cloud SQL) is close to 65% waste for that environment.

How it forms. Shutdown has no owner, and one broken Monday-morning startup buys the initiative a year of "we tried that."

Find it. In Billing → Reports, group by Project and compare each non-prod project against what a business-hours schedule would cost. Then fix it with instance schedules, which handle both the stop and the politically crucial morning start:

gcloud compute resource-policies create instance-schedule nonprod-hours \
  --region=us-central1 \
  --vm-start-schedule="0 8 * * 1-5" \
  --vm-stop-schedule="0 20 * * 1-5" \
  --timezone="America/Chicago"

Attach the policy to non-prod VMs and the Monday startup problem disappears. Cloud SQL supports on-demand stop and start, and GKE non-prod node pools can scale to zero off-hours.

What it's worth. Non-prod commonly runs 20 to 40% of total spend at mid-market companies, and scheduling cuts that portion by half or more.


Manual review versus continuous detection

Monthly manual reviewContinuous detection
Age of a findingAbout 15 days on average, often moreHours
Projects coveredThe top five by spendEvery project in the organization
Recommender useRead once, acted on weeks laterRead daily, verified before it's stale
Engineer timeMost of a day, every timeReview and approve only
OutputA spreadsheet and some ticketsFindings with evidence and a proposed fix

The checks are the same in both columns. What changes is how many projects get looked at, and how long waste bills before anyone does.

Why monthly GCP cost reviews keep failing

Add the ranges up and a realistic mid-market deployment carries 20 to 35% addressable waste. The natural response is a monthly review meeting, and on Google Cloud it underdelivers for a specific reason: the findings already exist, and the review is the slow part.

01Day 0

Waste starts

A VM is deleted and its data disk keeps billing.

02Days later

Recommender sees it

An idle-resource finding appears on a page nobody visits.

03Day 30+

Review

Someone opens Billing reports for the top five projects and files a ticket.

04Day 45+

Maybe fixed

If the disk was in a reviewed project, and the ticket found an owner.

On Google Cloud the finding often exists within days. The latency is in the looking.

Recommender findings are perishable. A rightsizing suggestion pulled during your review reflects the last 8 days. By the time the resize ticket reaches a sprint three weeks later, the recommendation may have changed or gone stale.

Projects multiply. A 40-project organization needs 40 passes, or scripting, or an honest admission that only the top five ever get reviewed. Waste migrates to the projects nobody audits.

Latency has a price. A monthly review means the average finding is 15 days old before anyone sees it. On a $100K-a-month bill with 20% addressable waste, each month of detection delay costs roughly $20K. The review takes an engineer most of a day, so it gets skipped in release-heavy months, which are the months that create the most waste.

The fix isn't reviewing harder. It's making detection continuous. Start by hand: part two turns the checks above into a repeatable audit built on Billing reports, Recommender, Cloud Monitoring and the BigQuery billing export. Then part three shows CloudThinker Optimize running all seven checks continuously across every project through read-only access, with every remediation starting in Manual and waiting for your approval.

Related reading

Start free with 100 premium credits and no card required, or continue with the DIY GCP cost audit.

gcpcostoptimizationfinops