· 4 min read · ← All posts
BigQuery FinOps GCP Reservations

Slot reservations — the math and the politics

Capacity-based slot reservation is the single biggest one-time FinOps lever in BigQuery for workloads with predictable diurnal patterns. Here is the math, the sizing process we used, and the two organisational traps that have killed more reservation moves than the math has.

The math

On-demand: $5 per TB scanned (US pricing as of mid-2026, varies by region). You pay per byte regardless of how much capacity you used.

Capacity (Enterprise edition): ~$0.06 per slot-hour, baseline + autoscale. 100 baseline slots running 24×7 is roughly $4,400 / month plus autoscale up-charge for bursts.

Break-even depends on workload shape. For a predictable batch workload scanning ~1 PB / month on-demand, that’s ~$5,000 / month. A 100-slot reservation that absorbs that workload at maybe 60-70% utilisation costs ~$4,400 — already cheaper. Add the autoscale band on top for the peaks and you save 20-30%.

For interactive ad-hoc workloads with low total bytes scanned, the math goes the other way. Reservation idle time is real cost.

The sizing problem

You can’t size a reservation correctly by averaging the on-demand spend. The reservation needs to absorb the peak of the regular workload, not the mean. Two complications:

  1. Concurrency. A query that scans 100 GB might use 200 slots for 30 seconds or 50 slots for two minutes. The per-query slot-time integral is what matters, and INFORMATION_SCHEMA tells you both pieces.

  2. Burst patterns. A reservation that’s flat 24×7 wastes money overnight. A reservation with autoscale handles bursts but the autoscale charge is on top of baseline; the question is what baseline to set so autoscale fires rarely enough.

The sizing query I run first:

SELECT
  TIMESTAMP_TRUNC(creation_time, HOUR) AS hour,
  SUM(total_slot_ms) / (1000 * 60 * 60) AS slot_hours,
  COUNT(*) AS jobs
FROM `region-us`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
WHERE creation_time > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 90 DAY)
GROUP BY hour
ORDER BY hour;

Plot it. The 95th percentile of the hourly slot-hour is roughly the baseline you want. The peak above that is where autoscale lives.

The first organisational trap

The reservation purchase is a one-year commitment (or three, with a bigger discount). The engineering team can sign off on the recommendation, but the contract goes through finance and procurement. That’s a 2-4 week loop minimum.

In the gap, two things happen:

Mitigation: do the contract negotiation in parallel with the engineering sizing, not after. Set a calendar reminder for +30d to re-validate the reservation against actual usage. Don’t let the purchase be the end-state.

The second organisational trap

Reservations are billed monthly regardless of who runs the queries. If multiple teams share a project, the shared reservation needs an attribution scheme. Otherwise:

We solved this with project-level reservation assignments where politically possible, and with labels + a Looker dashboard that showed slot_hours_consumed_by_team where the projects were already shared. The dashboard alone — without changing billing — shifted behaviour within a quarter.

When NOT to reserve

The right order is: optimise first, reserve second. The optimisation typically removes 20-40% of the workload, and that’s a 20-40% smaller reservation you commit to.

The 30-day check

After a reservation purchase, the +30d check is the most important moment. The questions to answer:

If the baseline is idle most of the time and queries aren’t queuing, you over-bought. Most reservation contracts allow downward changes with notice; use them.

If queries are queuing at peak, you under-bought the baseline. Add slots or extend the autoscale ceiling.

The reservation is not a fire-and-forget purchase. Treat it the same way you’d treat any other infrastructure commitment — with a monthly review.

← Back to all posts