BigQuery Cost Control That Scales: 12 Patterns from Real Warehouses
Google Cloud·By Islam Gamal··34 min read

BigQuery Cost Control That Scales: 12 Patterns from Real Warehouses

Twelve battle-tested patterns to cut BigQuery spend 30–70% without touching data quality — partitioning, clustering, materialized views, reservations, and the FinOps loop that keeps savings from decaying.

Islam Gamal
Islam Gamal
AI · Data · Automation Engineer
TL;DR

BigQuery bills you on storage and bytes scanned (or slots). 90% of surprise bills are unbounded scans against wide, unpartitioned tables. Fix partitioning + clustering + SELECT * first, then layer materialized views, BI Engine, and right-sized reservations. Wrap it in a FinOps loop — labels, billing export, weekly review — so savings compound instead of decaying.

Key takeaways
  • Partition every fact table on event/ingest date and set require_partition_filter=true.
  • Cluster on the 1–4 columns you filter or join on most — free win, no query changes.
  • Kill SELECT * with a CI linter; enforce column projection at review time.
  • Materialized views + BI Engine cover the top 20 dashboard queries at a flat cost.
  • Attribute every dollar with query labels + billing export, and run a weekly FinOps review.

Know what you're paying for

BigQuery bills you on two axes: storage (cheap, split into active vs long-term) and compute — either on-demand ($/TB scanned) or slot-based reservations (BigQuery editions). Everything in this playbook maps to one of those two levers.

Before optimizing anything, spend one hour with INFORMATION_SCHEMA.JOBS_BY_PROJECT and your billing export. You'll almost always find that the top 10 queries account for 60–80% of spend. Fix those first; the long tail is noise.

The 12 patterns, ranked by ROI

  1. Partition every fact table on ingestion timestamp or event date. Turn on require_partition_filter=true so accidental full scans fail loudly instead of silently costing $2k.
  2. Cluster on the 1–4 columns you filter or join on most. Zero query changes, up to 10× cheaper scans.
  3. Kill SELECT * — enforce with a linter in CI. Projection is the single biggest lever on wide event tables.
  4. Approximate aggregates (APPROX_COUNT_DISTINCT, APPROX_QUANTILES) for dashboards. 10–100× cheaper, error < 1%.
  5. Materialized views for the top 20 dashboard queries. Auto-refresh, transparent rewrites when the optimizer sees a match.
  6. BI Engine reservation for Looker Studio dashboards — sub-second queries, flat monthly cost, no per-scan surprises.
  7. Table sampling during development: TABLESAMPLE SYSTEM (1 PERCENT). Saves you and the shared reservation.
  8. Session / temp tables for exploratory work — dropped automatically, no long-term storage bill.
  9. Search indexes for needle-in-haystack log queries. 20× faster than LIKE '%foo%' on TB-scale logs.
  10. Right-size reservations — start with editions autoscaling, then buy 1-year commitments once demand stabilizes.
  11. Cross-region audits — egress between multi-region datasets is a silent killer. Co-locate storage with compute.
  12. Query labels + billing export — attribute every dollar to a team, workflow, or dashboard. Without labels, cost control is guesswork.

Partition + cluster like you mean it

Partitioning splits a table into daily (or hourly) chunks; clustering physically sorts within each partition. Together they let the optimizer skip 99% of the table for typical queries.

create table analytics.events (
  event_id string,
  user_id string,
  event_type string,
  country string,
  event_ts timestamp,
  payload json
)
partition by date(event_ts)
cluster by event_type, country, user_id
options (
  partition_expiration_days = 400,
  require_partition_filter = true
);

Rules of thumb:

  • Partition on the column you filter by 90% of the time (usually event date).
  • Cluster keys go from most-selective to least. Don't cluster on high-cardinality free text.
  • Set partition_expiration_days to auto-drop cold partitions if the business allows.
  • Turn on require_partition_filter — you'll catch bad queries at development time, not at end-of-month.
Pro tip: Backfilling into a partitioned table? Use MERGE with a partition filter in the ON clause; otherwise you rewrite the entire target.

Queries you should never run

-- ❌ full scan, no filter, no projection
select * from events;

-- ❌ regex on unindexed text against TBs of logs
select * from logs where regexp_contains(message, r'timeout');

-- ❌ cross-join on a wide table
select a.*, b.* from big_a a cross join big_b b;

-- ❌ CURRENT_TIMESTAMP() in a WHERE that defeats partition pruning
select * from events where event_ts > current_timestamp() - interval 1 day;

-- ✅ scoped, projected, partition-aware
select event_id, user_id, event_type
from events
where event_date between date_sub(current_date(), interval 7 day)
                      and current_date()
  and event_type in ('signup','purchase');

The current_timestamp() anti-pattern is subtle: BigQuery can't prune partitions on non-constant expressions in some versions of the optimizer. Cast to current_date() or use a scripting variable computed once.

Materialized views + BI Engine: the flat-cost tier

Once you know your top 20 dashboard queries (from INFORMATION_SCHEMA), turn each into a materialized view. The optimizer transparently rewrites qualifying queries to hit the MV instead of the base table.

create materialized view analytics.daily_active_users
partition by activity_date
cluster by country
as
select
  date(event_ts) as activity_date,
  country,
  approx_count_distinct(user_id) as dau
from analytics.events
where event_type in ('login','session_start')
group by 1, 2;

Layer BI Engine on top for Looker Studio. It caches hot query results in memory; sub-second latency, and the monthly reservation is often cheaper than the on-demand cost it replaces.

On-demand vs editions: pick with data, not vibes

WorkloadBest fitWhy
Spiky, unpredictableOn-demandPay per TB; zero commitment.
Steady + predictableEditions + autoscalingSlot-hours are cheaper than $/TB at scale.
Heavy dashboardsEditions + BI EngineFlat cost, cache hit rates near 100%.
ML training / batchEditions with 1y commitHighest discount, tolerant of queue latency.

Rule of thumb: switch to editions once you can predict monthly TB scanned within ±30%. Start with a small baseline + generous max slots, then trim after two billing cycles.

Attribute every dollar (FinOps in a week)

  1. Enable the billing export to BigQuery.
  2. Tag every query with labels: team, workflow, env, dashboard. dbt, Airflow, and n8n all support labels on the query API.
  3. Build a single materialized view that joins INFORMATION_SCHEMA.JOBS_BY_PROJECT with billing on job ID.
  4. Pipe it into Looker Studio with filters by team + dashboard. Ship a weekly digest to owners.
  5. Set a per-team budget alert at 80% of last month's baseline.
select
  labels.value as team,
  date(creation_time) as day,
  sum(total_bytes_billed) / pow(1024,4) as tb_billed,
  sum(total_slot_ms) / 1000 / 3600 as slot_hours,
  count(*) as jobs
from `region-us`.INFORMATION_SCHEMA.JOBS_BY_PROJECT j,
     unnest(labels) as labels
where labels.key = 'team'
  and creation_time > timestamp_sub(current_timestamp(), interval 30 day)
group by 1, 2
order by 2 desc, 3 desc;
Watch out: Never bill teams from raw INFORMATION_SCHEMA without a materialized cache — the metadata queries themselves cost slot-hours and will show up in your own report.

The weekly FinOps loop

Cost control decays the moment attention moves elsewhere. Institutionalize a 30-minute weekly ritual:

  1. Top 10 by cost — the biggest queries of the week. Owner named. Fix or accept.
  2. Top 10 by growth — queries whose cost grew >50% week-over-week. Usually a new dashboard or a bad JOIN.
  3. Unlabeled spend — anything >$50 without labels. Chase the owner and label it.
  4. Idle reservations — reservations under 40% utilization for two weeks are ready to shrink.
  5. New MV candidates — repeated identical query shape from >3 sources? Materialize it.

Track the exact same five items every week for a quarter. The graph of savings vs baseline is the artifact you show leadership.

Common failure modes and how to unstick them

SymptomLikely causeFix
Sudden 5–10× cost spikeNew dashboard scanning unpartitioned tableForce partition filter, add MV
MV never used by optimizerQuery shape doesn't match MV definitionSimplify query or add second MV
Reservation queue latencyAutoscaling max too low or slot-hungry ML jobSplit ML into own reservation
Cross-region egress billDataset in US, compute in EUMove dataset or use scheduled transfer
INFORMATION_SCHEMA queries expensiveFull 6-month scan on every dashboard loadCache to a daily MV

How this fits your broader stack

Cost control on the warehouse is only half the picture. Pair it with the same discipline on your AI workloads and pipelines:

#BigQuery#Cost#FinOps#Data Engineering#GCP

Frequently asked

On-demand or slots?

On-demand until you can predict monthly TB scanned within ±30%. After that, editions with autoscaling almost always beats on-demand for the same workload. Add 1-year commitments once your baseline is stable.

Do materialized views help with high-cardinality group-bys?

Yes — as long as the base table is partitioned and clustered on the same keys and the refresh cadence matches the freshness SLA. Otherwise the refresh cost eats the query savings.

How do I stop developers from accidentally scanning a whole fact table?

Set require_partition_filter=true on the table, add a CI linter (sqlfluff or a custom dbt macro) that rejects SELECT *, and give every developer read access via a service account with a per-user byte-billed quota.

Are BI Engine reservations worth it for small teams?

If a handful of Looker Studio dashboards drive most of your on-demand spend, yes — even a 2GB reservation typically pays for itself. Measure first with a two-week trial.

Building something similar?

I help teams ship production-grade AI agents, n8n workflows, and data platforms. Let's talk about what you're building.

Work with me
Islam Gamal
Written by

Islam Gamal

AI, Data & Automation Engineer. I design and ship production AI agents, n8n workflows, and cloud data platforms — with a focus on reliability, cost, and measurable business impact. Founder of Tashghil and Tek bil Arabi.

More from Islam Gamal

Browse every article by Islam Gamal on the author page.