Monitoring tells you what you spent yesterday. Guardrails stop the expensive query before it runs. This tutorial layers four controls on BigQuery on-demand usage, from a single query up to the billing account.
1. Dry runs: know the cost before you run
A dry run validates a query and reports bytes it would process, at no charge.
bq query --use_legacy_sql=false --dry_run \
'SELECT user_id, event_name FROM `my-proj.analytics.events` WHERE event_date = "2026-01-15"'
On-demand cost is roughly bytes processed divided by 2^40, times your region's per-TiB rate. Check current pricing rather than hard-coding it. Note that a dry run reflects the bytes after partition pruning, so it is a good way to confirm your filter on the partition column is working. Clustering benefits are often not reflected in the estimate, so actual billing can be lower.
2. Maximum bytes billed: a hard per-query ceiling
Set a limit and BigQuery fails the query up front if it would exceed it, without charging.
bq query --use_legacy_sql=false --maximum_bytes_billed=10000000000 \
'SELECT * FROM `my-proj.analytics.events`'
The same setting exists in client libraries as maximum_bytes_billed on the job configuration. In Python:
from google.cloud import bigquery
client = bigquery.Client()
cfg = bigquery.QueryJobConfig(maximum_bytes_billed=10 * 1024**3)
client.query(sql, job_config=cfg).result()
Bake it into shared wrappers, dbt profiles (maximum_bytes_billed in the BigQuery profile), and scheduled-query tooling so every automated path has a ceiling. Pick a value per workload: a tight limit for BI service accounts, a larger one for batch ELT.
3. Custom quotas: daily caps per project and per user
In the Google Cloud console, open IAM & Admin > Quotas & System Limits, filter for BigQuery API, and find Query usage per day and Query usage per day per user. Edit these to set a custom limit in TiB. When the cap is reached, further on-demand queries fail with a quotaExceeded error until the quota resets (daily, Pacific time).
Guidance:
- Set the per-user cap lower than the project cap so one analyst or notebook loop cannot consume the whole day's allowance.
- Use separate projects for ad hoc exploration, BI, and pipelines so a cap on one does not break the others.
- Remember quotas only govern on-demand bytes. If a project runs on an Editions reservation, spend is bounded by your autoscaler maximum slots instead, so set that deliberately.
Alert on quotaExceeded in Cloud Logging so a cap that silently breaks a dashboard is noticed quickly.
4. Budget alerts: catch the slow leak
In Cloud Billing, create a budget scoped to the BigQuery service (and optionally specific projects), with thresholds at 50, 90, and 100 percent of expected spend plus a forecasted-spend alert. Send notifications to a Pub/Sub topic, not only to email, so you can automate the response.
Budgets alert; they do not stop spend by themselves. A common pattern is a Cloud Run function subscribed to the topic that, at a defined threshold, lowers a project's custom quota or pages the on-call owner. Keep any automatic action conservative and reversible.
5. Find who breaks the rules
Verify the guardrails with job metadata:
SELECT user_email, COUNT(*) AS jobs,
ROUND(SUM(total_bytes_billed) / POW(1024, 4), 2) AS tib_billed
FROM `region-us`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
WHERE creation_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY)
AND job_type = 'QUERY'
GROUP BY user_email
ORDER BY tib_billed DESC;
Adjust the region qualifier to match your data. Review weekly and tune limits.
Rollout checklist
- Add
maximum_bytes_billedto every service account's tooling. - Require partition filters on large tables (
require_partition_filter = TRUE). - Set per-user and per-project custom quotas.
- Create budgets with Pub/Sub notifications.
- Review the INFORMATION_SCHEMA report weekly.
Need help designing cost controls or moving a team to Editions? Get in touch with BQBrains.