Your first 10 BigQuery cost guardrails (before the bill, not after)

· sets the byte ceiling before the first analyst query🛡️ with ten controls in effort order, not a lecture

Published August 28, 2026

The 60-second version

  • One typo can cost $250 with no warning. Nothing in BigQuery defaults stops a 40 TB full-table scan.
  • Four controls take an hour and prevent most surprise bills: a byte ceiling, daily quotas, required partition filters, billing alerts.
  • Dashboards should read rollups, never raw events. One nightly aggregate replaces thousands of full scans.
bottom line: make expensive queries fail loudly

Every BigQuery horror story has the same shape. Somebody wrote a query, somebody scheduled it, and nobody found out what it cost until the invoice arrived four weeks later. By then it had run 720 times.

The fix is not "be careful". Careful does not survive contact with a new analyst, a connected BI tool, or an AI assistant generating SQL on demand. The fix is a set of controls that make expensive queries fail loudly instead of running quietly.

Here are ten, ordered by saving per hour of effort. The first four take under an hour combined and prevent the large majority of surprise bills.

Somebody wrote a query

A new analyst, a BI tool, or an AI assistant generates SQL with no cost intuition and no memory of last month's bill.

Somebody scheduled it

The query lands on a cron or a dashboard refresh. One run costs pennies, so nobody reviews what 720 runs cost.

Nobody finds out for weeks

The invoice arrives four weeks later. By then the typo has run hundreds of times and the money is gone.

The mental model: on-demand BigQuery bills for bytes scanned, not rows returned, not query complexity, and not time taken. Every guardrail below is either a hard limit on bytes, a structural change that reduces bytes, or an alarm that tells you bytes moved. Nothing else matters to the bill.


The four that take an hour

1. Set maximum_bytes_billed as a project default

This is the highest-leverage control in BigQuery and the most consistently skipped. It converts a surprise bill into an error message.

Project-Level Default Byte Ceiling

Pick a ceiling slightly above your legitimate heaviest routine query. If your normal working queries scan 10–20 GB, set 100 GB. The analyst who accidentally writes a full-table SELECT * gets an error in two seconds rather than a $60 charge, and — importantly — learns something, because the error names the byte figure.

Without this, one mistake is unbounded. A query against a 40 TB events table with no date filter costs roughly $250 to run once. There is nothing in BigQuery's defaults that stops it. maximum_bytes_billed is the only thing standing between a typo and that charge.

2. Custom quotas per user and per project

maximum_bytes_billed caps a single query. Custom quotas cap the daily total, which is what catches a loop, a misconfigured scheduler, or a BI tool refreshing far more often than anyone intended.

Set these in IAM & Admin → Quotas: Query usage per day at the project level, and Query usage per day per user. Numbers that work for a typical mid-sized setup: 2 TB/day per project, 500 GB/day per user. Both are adjustable, and hitting one produces a clear error rather than a bill.

The per-user quota matters more than people expect, because it isolates blast radius. One person's runaway notebook cannot consume the whole team's budget.

3. Require a partition filter on your largest tables

This makes it structurally impossible to scan a whole table by accident:

Force Every Query to Filter on the Partition Column

Now SELECT * FROM analytics.events fails with an explicit message telling the user to add a filter on event_date. Every query against your biggest table is forced to prune. This one option has probably saved more money than any other single line of BigQuery configuration.

4. A billing alert, at a threshold you will actually notice

Cloud Billing → Budgets & alerts. Set a monthly budget with alerts at 50%, 90% and 100%. Critically, route them to a channel a human reads — a shared inbox that nobody opens is the same as no alert.

Set the budget at roughly 1.3x your normal month. Too tight and you train yourself to ignore it; too loose and it fires after the damage is done. alerts to an inbox nobody opens are decoration

Your first-hour checklist under an hour, combined →

  • Set maximum_bytes_billed as a project default — slightly above your heaviest legitimate routine query, so accidents fail in two seconds with the byte figure named.
  • Add custom daily quotas — per project and per user, so one runaway notebook or scheduler cannot spend the whole team's budget.
  • Require a partition filter on your largest tables — so forgetting the date predicate is a plan-time error, not a full-table bill.
  • Route a billing alert to a channel a human reads — at 50%, 90% and 100% of roughly 1.3x a normal month.

The five that take an afternoon

Structural Guardrails

Process Flow
5

Partition and cluster every table over ~1 GB

Partition on the date column you filter by most; cluster on the two or three columns you filter or join on after that. Without partitioning, every query is a full scan by definition.

6

Materialise the aggregates your dashboards read

A dashboard should query a pre-aggregated daily table, never a raw events table. One scheduled rollup replaces thousands of expensive tile refreshes.

7

Set partition expiration on raw data

Raw event data older than two years is almost never queried and always billed for storage. Expiration deletes it automatically.

8

Audit scheduled queries quarterly

Scheduled queries are where cost compounds silently — the same bytes, every day, forever, long after anyone needed the output.

9

Give BI tools a restricted service account

A separate account with its own quota and access only to aggregate tables. A dashboard tool should not be able to reach a raw events table at all.

5. Partition and cluster

Partitioning splits a table into date-based segments so a filtered query reads only the segments it needs. Clustering sorts data within each partition so filters on the clustered columns prune further.

The rule of thumb: partition on the column in your WHERE clause; cluster on the columns in your JOIN and secondary filters. On a typical marketing events table, that is partition by event_date, cluster by (campaign_id, event_name).

6. Materialise what dashboards read

This is the single largest recurring saving in most setups. A dashboard tile that queries raw events costs its full scan on every refresh, for every viewer. The same tile reading a pre-aggregated daily table costs a rounding error.

One Scheduled Rollup Replacing Thousands of Raw Scans

The tile that cost more than the BI licence

Before / after
Before2.4 TB per refreshRaw events table, no date filter, hourly auto-refresh
Before cost~$10,900/month730 refreshes × 2.4 TB × $6.25/TB
After0.4 GB per refreshSame tile, reading the daily rollup
After cost~$2/monthPlus ~$4/month for the nightly rollup job itself
A 99.9% reduction from one scheduled query and a pointer change in the dashboard. The rollup took twenty minutes to write. This pattern is the reason 'materialise what dashboards read' is on every BigQuery cost list ever written — and it is still the most commonly skipped one.
Same tile, same answer: ~$10,900 a month reading raw events versus ~$6 reading a rollup.

7. Partition expiration

Expire Raw Partitions You Will Never Query Again

Storage is cheaper than query, which is why it gets ignored — but on a multi-terabyte events table it is a real monthly line, and 400-day-old raw events have essentially zero query value once you have rollups. Keep the aggregates forever; expire the raw.

8. Audit scheduled queries quarterly

Scheduled queries are where cost hides, because nobody watches something that already works. Query your own job history to find them:

Your Most Expensive Recurring Jobs, Last 30 Days

Run this once a quarter. The top five rows are almost always where 80% of your spend is, and at least one of them is usually a report nobody reads any more.

9. A restricted service account for BI tools

Connected BI tools are a common source of runaway cost because they refresh on their own schedule and often ignore query caching. Give each one a dedicated service account with:

  • Access only to your aggregate dataset, not raw tables.
  • Its own Query usage per day per user quota.
  • A conservative maximum_bytes_billed default.

Then a misconfigured refresh interval costs you a quota error instead of a month of scans.


Make cost visible before running

The other nine are limits. This one is culture, and it is what stops you needing the limits.

Controls 1–9 · the safety net

🧱 The nine limits

Make expensive queries fail. Byte ceilings, quotas, required filters and alerts turn surprise bills into error messages.

  • Fail loudly instead of billing quietly
  • Work while nobody is thinking about cost
  • Need no habit change to help
Control 10 · the habit

👀 The culture

Make cost visible before the query runs. Every person writing SQL sees the byte estimate first — human or machine.

  • The validator line is the price tag
  • Dry runs are free, so showing them is free
  • Stops you needing the limits at all

Every person writing SQL should see the byte estimate before pressing run. In the BigQuery console it is already there — the validator line in the top right, updating as you type. Most people never look at it, because nobody ever told them it was the price tag.

For programmatic access, the dry run gives the same figure for free:

bq query --dry_run --use_legacy_sql=false \
  'SELECT campaign_id, SUM(revenue) FROM analytics.events WHERE event_date >= "2026-08-01" GROUP BY 1'

This matters more as AI-generated SQL becomes normal. A generated query is written by something that has no cost intuition and no memory of last month's bill. Any tool producing SQL against your warehouse should show the byte estimate alongside the query, before execution — and the estimate is free to obtain, so there is no excuse for not showing it.

Takeaways for your next report

  • BigQuery bills for bytes scanned, so every guardrail is a byte limit, a byte reducer, or a byte alarm — nothing else touches the bill.
  • The first hour buys the most: a project byte ceiling, per-user daily quotas, required partition filters, and an alert a human reads.
  • Dashboards must read pre-aggregated rollups, never raw events — one nightly job replaces thousands of full scans.
  • Audit scheduled queries quarterly from INFORMATION_SCHEMA, lock BI tools to aggregate datasets, and show the byte estimate before every run.
stick this on your Monday report
Free tool

BigQuery Cost Estimator

Estimate what a BigQuery query will cost before you run it, from validator bytes, with a table-scan simulator and a paste-your-query cost audit.


Start here if time is short

Where to Start

Reporting Hierarchy
Tier 1
Hour one — stop the bleeding

maximum_bytes_billed as a project default, custom daily quotas per user and project, require_partition_filter on your largest tables, and a billing alert routed somewhere a human reads.

Prevents the large majority of surprise bills
Tier 2
Afternoon two — structural savings

Partition and cluster everything over a gigabyte, materialise the aggregates dashboards read, and set partition expiration on raw data.

Cuts recurring cost, often by an order of magnitude
Tier 3
Ongoing — keep it from returning

Quarterly scheduled-query audit from INFORMATION_SCHEMA, restricted service accounts for BI tools, and byte estimates visible before every run.

Stops the drift that undoes the first two

Frequently asked questions

Will maximum_bytes_billed break legitimate queries?

Occasionally, and that is the point — you find out at the moment it happens, with a message naming the byte figure, rather than in an invoice. Set it above your heaviest routine query, and let people raise it per-session for the rare job that needs it.

Does query caching help?

Yes, substantially, and it is free — identical queries against unchanged tables return cached results at no cost for 24 hours. It fails silently though: any non-deterministic function (CURRENT_TIMESTAMP(), RAND()), any wildcard table, or any change to the underlying data invalidates the cache. Dashboards that inject a timestamp into every query defeat it entirely.

Should I switch to capacity (flat-rate) pricing?

It becomes attractive at high, steady volume — you buy slots and cost stops depending on bytes at all, which caps the downside of a badly-written query. But it also caps your throughput, so an expensive query slows everything else rather than costing more. Fix the guardrails first: many teams that were considering the switch find on-demand is fine once the rollups exist.

How do I find which queries cost the most historically?

INFORMATION_SCHEMA.JOBS_BY_PROJECT holds 180 days of job history including total_bytes_billed per job. The audit query above is the starting point; group by user_email alone for a per-person view.

Does LIMIT reduce cost?

No. LIMIT truncates the result after the scan is complete and paid for. The only things that reduce bytes are selecting fewer columns, filtering on a partition column, and reading from a smaller table.

One question to check the whole post landed. if this is easy, the guardrails worked

An analyst's query scans a 40 TB events table with no date filter. What actually stops the $250 charge?


The summary

Ten controls, in order of return per hour:

  1. maximum_bytes_billed as a project default — turns surprise bills into errors.
  2. Custom daily quotas, per project and per user.
  3. require_partition_filter on your largest tables.
  4. A billing alert routed to a channel someone reads.
  5. Partition and cluster every table over a gigabyte.
  6. Materialise the aggregates dashboards read — usually the biggest recurring win.
  7. Partition expiration on raw data.
  8. Quarterly scheduled-query audit from INFORMATION_SCHEMA.
  9. Restricted service accounts for BI tools.
  10. Byte estimates visible before every run, for humans and for anything generating SQL.

The first four take an hour. Do those today, and the rest becomes optimisation rather than firefighting.

Chinmay Raibagkar

Chinmay Raibagkar

About author →

Founder of DataLens AI. He helps non-technical teams read their ad and database numbers with confidence — which number to trust, what to do next, and what to ignore.