BigQuery slots explained: usage, contention, and cost

A slot is compute, a slot-hour is a billing line, and contention is demand sitting on the ceiling. Here is how BigQuery capacity works.

Banner reading BigQuery slots explained, a slot board with a contention gauge and a query queue

What a slot actually is

A BigQuery slot is a unit of computational capacity, roughly a virtual CPU that can execute a slice of a query. There is no VM to provision, no machine type to pick, nothing to SSH into. You either share a Google-managed on-demand pool and pay per tebibyte scanned, or buy a pool of slots (Editions / capacity) and pay for that pool by the hour.

When SQL is submitted, BigQuery compiles it into an execution plan: stages, with workers inside those stages. Each worker consumes some number of slots for some number of milliseconds, and the job's total_slot_ms is the integral of that: how much slot-time the query used, not how long anyone waited for it in the UI.

The conversion that matters for money is 3,600,000 slot-ms per slot-hour. A query that holds 100 slots for six minutes uses 10 slot-hours (100 × 0.1), and a query that holds 10 slots for an hour also uses 10 slot-hours. Wall-clock time and slot-time are different meters; teams that optimize 'the dashboard that takes 40 seconds' without looking at slot-ms are often optimizing the wrong number.

Wall-clock is not the bill
SQL
-- 100 slots × 6 minutes = 10 slot-hours
-- 10 slots × 60 minutes = 10 slot-hours
SELECT
  job_id,
  total_slot_ms,
  total_slot_ms / 3600000 AS slot_hours,
  TIMESTAMP_DIFF(end_time, start_time, SECOND) AS wall_clock_s
FROM `region-us`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
WHERE creation_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY)
  AND job_type = 'QUERY'
  AND state = 'DONE'
ORDER BY total_slot_ms DESC
LIMIT 20;

On-demand jobs still generate slot-ms, you simply are not billed for it. BigQuery still schedules workers from a shared pool, and a project still has concurrency and on-demand slot caps. That is why a badly written on-demand query can cost a fortune in bytes and feel slow at the same time: it is fighting for slots in a pool it does not control, while paying $6.25 per TiB for whatever storage it managed to read.

Concurrency is not slots. A project can have many jobs waiting for a concurrency slot while the reservation still has idle compute, or many jobs running with a sliver of the pool each, and total_slot_ms will not tell you which. JOBS_TIMELINE state PENDING vs. RUNNING will. Watching only job duration in a dashboard mixes both failure modes into a vague 'BigQuery is slow.'

Shuffle is slot work that is not scan. A 2 GiB join that explodes a key can burn more slot-ms than a 200 GiB well-clustered aggregation. On-demand, the bytes are already paid for, so the pain is runtime; on capacity, it is occupancy. That is why shuffle is not a line in estimated waste. The Usage page charts the top shuffled queries instead.

A slot is a unit of work, not a machine you can SSH into

BigQuery slices a query into stages. Workers in those stages consume slots for some number of milliseconds. Sum that up and you get slot-ms; divide by 3,600,000 and you have slot-hours — the unit Editions actually bills.

  1. 01

    SQL job

  2. 02

    Stages + workers

  3. 03

    Slots × time

total_slot_ms÷3,600,000 slot-ms = 1 slot-hour=slot-hours

100 slots for 6 minutes = 10 slot-hours

How slot usage is measured

Three INFORMATION_SCHEMA views tell the truth, and they answer different questions. JOBS_BY_PROJECT gives total_slot_ms per job, useful for spotting a high-usage model and for shuffle-heavy ratios (slot-ms versus bytes scanned), but it does not say whether those slots were available, contended, or idle in the reservation.

JOBS_TIMELINE_BY_PROJECT breaks the same work into time buckets (SlashPeak uses one-minute buckets). period_slot_ms in a one-minute bucket, divided by 60,000, is average concurrent slots during that minute, which is the number to plot against the reservation baseline. A 400-slot spike that lasted eight minutes disappears inside an hourly average, and it disappears inside a job-level total too.

Average concurrent slots per minute
SQL
SELECT
  TIMESTAMP_TRUNC(period_start, MINUTE) AS minute,
  SUM(period_slot_ms) / 60000 AS avg_concurrent_slots
FROM `region-us`.INFORMATION_SCHEMA.JOBS_TIMELINE_BY_PROJECT
WHERE period_start
  >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY)
  AND job_type = 'QUERY'
GROUP BY minute
ORDER BY minute;

RESERVATIONS_TIMELINE is the other side of the graph: assigned slots, max assigned, autoscale. Utilization is consumed divided by assigned, per hour, for this project. If three projects share a reservation, this project's JOBS_TIMELINE only sees this project's consumption. SlashPeak is explicit about that: underutilization can be overstated if siblings are busy, and contention can be understated if siblings are the ones pinning the pool. The scanner never invents the missing projects.

Peak concurrent slots are not average concurrent slots. SlashPeak's on-demand vs. Editions compare works minute by minute from JOBS_TIMELINE and sizes max slots from the 99th-percentile minute, so a baseline is not bought for the mean and then paged through every 9am dashboard refresh. Capacity planning without a peak number is how teams buy 80 slots, watch Monday morning overload, and then overcorrect to 500.

Minute buckets still smear a 10-second spike. That is acceptable for a 30-day compare and a utilization finding. It is not a profiler. Do not size a 1-year commitment off one 8-minute incident either.

On-demand projects often have empty RESERVATIONS_TIMELINE. That is expected. You can still sum slot-ms, still see PENDING, still compare Editions hypothetically. You cannot talk about 'our 200-slot floor' because there is not one. Empty is not zero assigned.

Usage is concurrent slots over time, not a single job's runtime

For a one-minute bucket, slot-ms consumed divided by 60,000 equals average concurrent slots. A 10-second burst that held 400 slots shows up as a high minute instead of being smeared across a quiet hour.

Consumed (this project)Reservation baseline

Avg ~28 slots

Peak ~48 slots

How slots are priced

On-demand compute is $6.25 per TiB scanned in US multi-region (first 1 TiB/month free), and slots are not on that invoice at all. Editions compute is slot-hours, with list prices in us-central1 of $0.04 (Standard PAYG), $0.06 (Enterprise PAYG), and $0.10 (Enterprise Plus PAYG). One- and three-year commitments discount those rates; Enterprise 3-year is $0.036 per slot-hour at list.

The arithmetic is unforgiving because you pay the floor. 100 Enterprise PAYG slots × $0.06 × 24 hours × 30 days = $4,320 per month, whether dbt ran or the warehouse was quiet. Autoscale above the baseline is additional slot-hours at the Edition rate, billed when used. Unused baseline is not credited back. That is the whole game: a baseline below actual demand shows up as contention and PENDING; a baseline above actual demand shows up as a line item you could have deleted.

The floor you pay even when idle
SQL
-- 100 Enterprise PAYG slots, billed whether used or not
-- 100 * 0.06 * 24 * 30 = $4,320 / month
SELECT
  100 AS baseline_slots,
  0.06 AS usd_per_slot_hour,
  100 * 0.06 * 24 * 30 AS monthly_floor_usd;

Commitments are sold in 50-slot increments with a 50-slot minimum. You cannot buy 37 slots. SlashPeak's underutilized-reservation recommendation rounds the suggested baseline up to that step from the p95 of per-minute consumed slots, then prices the delta at the Edition PAYG rate over a 720-hour month. It is a list-price estimate, not your CUD. Use it as the agenda for the conversation with whoever owns the commitment.

Standard Edition is cheaper per slot-hour and drops some Enterprise features (governance, VPC-SC, and similar). Enterprise Plus is the opposite. The cost conversation is not 'cheapest rate'; it is 'cheapest rate that still fits the features and the shape of demand.' A bursty BI project on Enterprise Plus baseline is usually the wrong Edition and the wrong billing mode at the same time.

PAYG without a baseline (or with a tiny one plus autoscale) tracks used slot-hours more closely than a fat commitment. It is also less predictable on the invoice. Finance that wants a flat number will push you to a floor. Engineering that just killed 4 TB scans will want PAYG for a month before anyone commits. Both are reasonable. Doing them in the wrong order is not.

The $750/month floor on the Editions vs on-demand compare exists so you do not migrate a toy project. Underutilized reservation has its own floors (assigned ≥ 50, enough hours of timeline, savings ≥ ~$50). If the delta is $40, the meeting costs more than the slots.

Editions list prices (us-central1) — you pay the floor every hour

PAYG is the sticker. One- and three-year commitments shave the rate. 100 Enterprise PAYG slots at $0.06/slot-hour is about $4,320/month whether the warehouse is busy or idle. Autoscale above baseline is extra; unused baseline is not a refund.

Standard

$0.04 PAYG

Enterprise

$0.06 PAYG

Enterprise Plus

$0.10 PAYG

100 Enterprise slots · 720 h = $4,320/mo$0.036 3-yr

Reservations, assignments, baseline, and autoscale

A capacity commitment is a billing object. A reservation is the pool jobs actually run in. Assignments map projects, folders, or the organization onto that reservation. If you only look at the commitment size, you will miss that three warehouses, a sandbox, and Looker are all drinking from the same 200 slots.

Baseline (sometimes called autoscale baseline / assigned slots) is the floor: slots that are reserved for this reservation and billed continuously. Autoscale max is the ceiling the reservation can burst to. Jobs above the baseline consume extra slot-hours; jobs that would go above the max wait or get squeezed, depending on scheduler state.

BigQuery's fair scheduler shares the pool among jobs in the reservation. A 6am dbt run that can absorb 400 slots and a 9am Looker explore that wants 80 INTERACTIVE slots do not politely take turns. They share. INTERACTIVE jobs are supposed to start quickly; BATCH jobs can wait. Putting both on one reservation is the usual reason a 'we have plenty of slots on average' project still has 40-second dashboards and a PENDING spike at 9:05.

Isolate BATCH warehouse from INTERACTIVE BI
SQL
-- Conceptual: two assignments, two pools
-- CREATE RESERVATION etl OPTIONS (slot_capacity = 200);
-- CREATE RESERVATION bi  OPTIONS (slot_capacity = 100);
-- CREATE ASSIGNMENT … reservation_id = 'etl' (proj-dbt)
-- CREATE ASSIGNMENT … reservation_id = 'bi'  (proj-looker)

-- Jobs API / scheduled query: priority = BATCH on etl
SELECT *
FROM `proj.analytics.fct_orders`
WHERE order_date = @run_date;

The operational move that beats buying more slots is splitting assignments: an ETL reservation for BATCH transformations, a BI reservation for INTERACTIVE, and on-demand or a tiny sandbox reservation for humans. Peak-hour ad-hoc spend is the clue that the third group is currently sitting in the second pool.

Folder-level assignments are how a new sandbox project ends up on the production pool without anyone noticing. Inventory assignments when utilization looks off in either direction; a 50-slot BI reservation that is 'overutilized' might be the entire org's notebooks because someone assigned the folder.

Ignore commitment size when debugging a slow dashboard. The dashboard's project assignment is the pool it runs in. A 2,000-slot org commitment with a 50-slot reservation assigned to Looker will still crawl. Buyers of commitments and owners of reservations are often different people. The scan has to name the reservation_id on the jobs, not the CUD.

Commitments feed reservations; projects are assigned to the pool

A reservation is the pool jobs actually run against: a baseline you pay for, an autoscale max they can burst to, and project/folder assignments that share it. Sizing the pool without looking at who is assigned is how you buy 500 slots for a project that averages 80.

Capacity commitment

Reservation

Baseline 200Autoscale max 500

proj-etl

proj-bi

proj-sandbox

Slot contention: demand sitting on the ceiling

Contention is not 'a query was slow.' It is a reservation-level condition: for a sustained share of hours, this project's consumed slots sit at or above capacity (assigned, max assigned, or autoscale max, whichever timeline column is the ceiling). Jobs still run, they just get fewer slots each, stages take longer, and everything sharing the pool feels it.

SlashPeak's slot contention rule is conservative on purpose. A minute counts as contended when consumed ≥ 95% of capacity. The finding emits when that is true for at least 20% of minutes in the scan window (and at least 8 hours of timeline). There is no dollar estimate. Raising the baseline might be right; isolating a SELECT * dashboard might also be right. Mixing those into 'estimated monthly waste' would invent a number you cannot defend.

Because the numerator is this project's JOBS_TIMELINE, sibling projects on the same reservation are missing. Contention can be under-detected, not over-detected. If SlashPeak says you are pinned, you are pinned. If SlashPeak says you are fine, another project might still be sitting on the ceiling.

The wrong response is to raise baseline every time someone complains about runtime. Check whether the contended hours are a handful of wide scans, MERGE rewrites, or human INTERACTIVE queries. Fix those first. Re-scan. If the contention finding is still there, then the pool is actually too small.

High severity when the contended hour share is even larger (on the order of 40%+). That is no longer 'Monday 9am.' That is a floor that does not match the work. Still not a $/mo. Still a capacity conversation after SQL and placement.

You can be underutilized on the mean and contended on a subset of minutes. Those two findings can coexist. Mean 24%, 20% of minutes pinned: spikey. Do not shrink; split or autoscale. Mean 24%, p95 35%, almost no minutes pinned: shrink in 50-slot steps.

Contention is demand sitting on the ceiling, minute after minute

SlashPeak marks a minute contended when this project's consumed slots are ≥ 95% of reservation capacity. If that is true for ≥ 20% of minutes, you have a sustained squeeze — jobs still run, they just get fewer slots each and take longer.

Billed as waste

Pinned to capacity

≥95% of capacity · many hours

After the fix

Healthy headroom

Peak below baseline · room to burst

Queuing is waiting to start, not running slowly

JOBS_TIMELINE rows are RUNNING or PENDING (among other states). PENDING means the job has been submitted and is not yet executing: it is waiting for slots, or for a concurrency slot, or both. That is a different failure mode from contention. Contention starves work that already started; queuing holds work at the door.

PENDING share of the timeline
SQL
SELECT
  COUNTIF(state = 'PENDING')
    / COUNTIF(state IN ('PENDING', 'RUNNING')) AS pending_share,
  COUNTIF(state = 'PENDING') AS pending_periods
FROM `region-us`.INFORMATION_SCHEMA.JOBS_TIMELINE_BY_PROJECT
WHERE period_start
  >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY)
  AND job_type = 'QUERY';
-- SlashPeak flags when pending_share >= 0.05 and pending_periods >= 60.

Contention can show up with almost no PENDING, where every job starts immediately and then crawls on a sliver of the pool. PENDING can show up with spare capacity, where a project concurrency limit is hit, or BATCH jobs are waiting their turn, or INTERACTIVE jobs are queued behind a fair-scheduler decision. Treating every delay as 'buy more slots' is how you end up paying for a bigger floor and still waiting.

PENDING only needs JOBS_TIMELINE, so you can see it even for on-demand projects where RESERVATIONS_TIMELINE is empty. SlashPeak's Usage page plots PENDING vs. RUNNING on its Query queuing chart. A pending share of 5% or more of pending plus running periods is worth a look; a single blip is not. Queuing has no dollar figure: it is a performance and SLA problem, not a billing SKU.

INTERACTIVE vs BATCH matters here. Moving the 6am warehouse to BATCH, or the 9am explores off the ETL reservation, often collapses PENDING without a purchase order. Peak-hour ad-hoc is the companion question: are human INTERACTIVE queries dominating local business-hours cost? Together they tell you whether the queue is a capacity problem or a working-hours problem.

On-demand PENDING is often the shared pool or a concurrency cap, not something you fix with a CUD. If on-demand jobs queue, reduce bytes (prune, project) so each job needs fewer workers, and stop submitting 200 INTERACTIVE notebooks at 9:01. Buying Editions because on-demand queued once is a non sequitur.

Around 15%+ pending share, users are waiting as a lifestyle. Still fix placement and SQL before you add 200 slots. Adding slots without splitting INTERACTIVE/BATCH can move PENDING into contention without making dashboards fast.

PENDING is waiting to start — not the same as running slowly

Contention starves running jobs. Queuing holds jobs in PENDING before they get slots or a concurrency slot at all. You can have one without the other. SlashPeak flags PENDING share ≥ 5% of JOBS_TIMELINE periods (advisory, no $/mo).

78%
22%

RUNNING · work is happening

PENDING · waiting on slots or concurrency

22% of timeline seconds in PENDING

Usage you should ignore, and usage you should not

Slot-ms on a single job is a useful smell, not a bill. Shuffle-heavy queries (high slot-ms versus bytes scanned) usually mean skew: a JOIN or GROUP BY that exploded a key. On-demand, that is a runtime problem, not a dollar problem, since the bytes are already paid for. On capacity, it is both: the job occupies the pool longer, which is how a 'cheap' scan becomes contention two jobs downstream. That is why SlashPeak charts the top shuffled queries on the Usage page instead of pricing them.

Cached results consume essentially no slots and no bytes. A dashboard that is fast because it is cached will look innocent in JOBS until someone changes a filter and the cache misses. Do not size a reservation from a week of cache hits.

Failed jobs can still consume slots. Long-running jobs that you cancel at minute 40 have already held capacity. SlashPeak surfaces failed and long-running jobs on the scan so they are not invisible; they are not priced as savings because the fix is operational (kill, timeout, rewrite), not a storage-layout change.

Median slot-ms/GiB for the project is the shuffle baseline, not the mean. Means get pulled by the outliers you want to detect. 2× median is a starting flag; 10× is a code review. A fixed global cutoff would punish warehouses that are mostly ETL and miss the one explode-join in a BI project.

A calm-looking mean is not a reason to ignore p95 concurrent slots or sibling projects on the same reservation. A single cancelled backfill is safe to ignore when sizing the floor, unless backfills are weekly and four hours long, in which case they are the floor.

  • On-demand: believe bytes billed first, slot-ms second (runtime and shuffle).
  • Capacity: believe per-minute consumed vs assigned first, then job-level slot-ms for the culprits.
  • Peak and p95 matter more than the mean. The mean is what makes a 500-slot baseline look 'only 40% utilized' while Monday 9am is on fire.
  • Sibling projects on the same reservation are missing from a single-project scan. If the number looks too good, check assignments.
  • Cache hits and failed jobs distort both meters. Split them out before you buy or shrink.

What to change, in an order that does not waste a commitment

Slot findings are political because someone signed a commitment. Treat the scan as the briefing, then move in this order.

SQL and placement before purchase orders. Stars, full scans, MERGE rewrites, and 9am notebooks create the shape of demand. Changing the floor without changing the shape is how you pay more for the same mess.

Then PAYG / baseline steps of 50, not a 3-year Plus jump. Prove p95 for a month. Then commit if finance needs a flat invoice.

Split reservations as soon as INTERACTIVE and BATCH share pain. That is cheaper than 200 extra slots on a mixed pool more often than it feels.

Re-scan after each step. Underutilized $/mo should fall when you lower the floor, and contention should not appear; if it does, you found the real p95 and should step back up one increment.

  • If you are on-demand and the Editions compare is cheaper at list, model your discount, then move the stable transformation project first, not the sandbox.
  • If baseline utilization is below ~40% and p95 is well under assigned, lower the baseline in 50-slot steps. Watch contention and PENDING after each step. SlashPeak's next scan is the scoreboard.
  • If you are pinned to capacity, do not raise the floor until you have checked SELECT *, MERGE rewrites, and peak-hour INTERACTIVE. Those jobs may be the entire spike.
  • Split reservations by workload: BATCH ETL, INTERACTIVE BI, humans elsewhere. Fair scheduling inside one pool cannot invent isolation.
  • Use BATCH for anything that can wait. INTERACTIVE is a latency SLA, not a default.
  • Autoscale max is for true bursts. It is not a substitute for a baseline you never measured, and it is not free.

How SlashPeak reads slots without reading your tables

The same metadata scan that catches missing partition filters also fetches per-minute JOBS_TIMELINE and RESERVATIONS_TIMELINE. Those two queries fail soft: on-demand projects, or a scanner key that cannot read the reservation admin project, get empty timelines instead of a failed scan. You will not get a fake utilization number from missing data.

Three rules and one chart share that input and answer four different questions. Compute pricing model (FLAT_RATE_CANDIDATE) asks whether on-demand bytes or Editions slot-hours would have been cheaper over the window. Under-utilized reservation asks whether the floor is too high, and is the one slot finding with a list-price $/mo estimate included in total estimated waste. Slot contention asks whether demand is pinned to the ceiling, and is an advisory. The Query queuing chart on the Usage page shows whether jobs are waiting to start.

The top shuffled queries chart sits next to those four, pointing at which jobs are causing the shape of demand rather than just confirming the shape is wrong. Attribution (human vs. service account, dbt vs. Looker vs. console, prod vs. staging) lives on the query findings, so the reservation conversation has a name attached.

None of this requires dataViewer. Slot timelines are catalog metadata. If a vendor needs to sample table contents to tell you the reservation is idle, they are solving a metadata problem with a data-access request. The free 30-day trial runs the full scan, including these checks, on a 30-day window.

Collapsing the four answers into one 'slot score' would hide real distinctions. A project can need a smaller floor (underutilized) while a sibling is contended. A project can be cheaper on Editions (flat-rate candidate) and still have PENDING from concurrency. One number would lie; four answers do not.

Four SlashPeak rules read the same timelines and answer different questions

Under-utilized baseline is the only one with a dollar estimate. Contention and queuing are advisories so they are not mixed into 'waste.' Compute pricing model compares the whole project to on-demand. Together they tell you whether to shrink, split, raise, or change meters.

01

Under-utilized reservation

Is the floor too high?

02

Slot contention

Are we pinned to the ceiling?

03

Query queuing

Are jobs waiting to start?

04

Compute pricing model

Wrong meter for this workload?

Worked example: 50-slot steps on a 500-slot floor

Reservation assigned 500 Enterprise PAYG slots. Floor = 500 × $0.06 × 720 = $21,600/month. Hourly consumed from JOBS_TIMELINE: mean 120, p95 180, max 240 for two hours in 30 days. Almost no hours at ≥95% of 500. PENDING share 1%. This is idle, not headroom.

Suggested baseline: round p95 180 up to 200 (50-slot step, min 50). New floor = 200 × $0.06 × 720 = $8,640. List-price delta = $12,960/month. Autoscale max can stay 300–400 for the 240 max if those two hours are real month-end, not a starred explore you are deleting tomorrow.

500 vs 200 vs 50-slot walk
SQL
SELECT
  500 * 0.06 * 24 * 30 AS floor_500,  -- $21,600
  200 * 0.06 * 24 * 30 AS floor_200,  -- $8,640
  (500 - 200) * 0.06 * 24 * 30 AS delta; -- $12,960
-- Next step down from 200 is 150, then 100, never 37.

If you instead bought 500 because p95 used to be 480 during a migration, and you already killed the full-table scans, do not keep 500 'in case.' The 50-slot step is how you walk down: 500 → 450 → 400, 48 hours of watching contention/PENDING each time if you are nervous, or 500 → 200 if the p95 is honestly 180 and you can autoscale.

Counterexample: mean 120, p95 470, 25% of hours ≥95% of 500. Underutilization should not fire (p95 is not well under assigned; you are pinned a quarter of the time). Buying more slots might be right after you check 9am INTERACTIVE. Shrinking to 200 would be an incident. Mean utilization 24% lied.

On-demand compare on the same project: 2,880 TiB × $6.25 = $18,000. A 200-slot floor at $8,640 beats on-demand. A 500-slot floor at $21,600 loses to on-demand. The wrong floor is worse than the 'wrong' model. 50-slot increments are how you stop oscillating between those two mistakes.

INFORMATION_SCHEMA diagnosis: JOBS, JOBS_TIMELINE, RESERVATIONS_TIMELINE

JOBS_BY_PROJECT: total_slot_ms, total_bytes_billed, reservation_id, cache_hit, priority if present. Rank jobs by slot-ms to find the heaviest models. Compute the shuffle ratio slot_ms / GiB. Majority reservation_id is the model.

JOBS_TIMELINE_BY_PROJECT: period_start, period_slot_ms, state, reservation_id. Per-minute SUM(period_slot_ms)/6e4 = concurrent slots. PENDING share as above. This view is the contention and queuing input.

Consumed vs PENDING by minute
SQL
SELECT
  TIMESTAMP_TRUNC(period_start, MINUTE) AS minute,
  SUM(period_slot_ms) / 60000 AS consumed_slots,
  COUNTIF(state = 'PENDING')
    / COUNTIF(state IN ('PENDING', 'RUNNING')) AS pending_share
FROM `region-us`.INFORMATION_SCHEMA.JOBS_TIMELINE_BY_PROJECT
WHERE period_start >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 14 DAY)
  AND job_type = 'QUERY'
GROUP BY minute
ORDER BY minute;

RESERVATIONS_TIMELINE: assigned, max assigned, autoscale, period. Align minutes with jobs timeline. Utilization = consumed / assigned. If this query fails, stop talking about idle floors.

Assigned capacity by minute (admin project)
SQL
SELECT
  TIMESTAMP_TRUNC(period_start, MINUTE) AS minute,
  reservation_id,
  AVG(slots_assigned) AS assigned,
  AVG(slots_max_assigned) AS max_assigned
FROM `region-us`.INFORMATION_SCHEMA.RESERVATIONS_TIMELINE
WHERE period_start >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 14 DAY)
GROUP BY minute, reservation_id
ORDER BY minute;

Plot them on one chart: assigned as a line, consumed as a line, PENDING share as a bar. The meeting is that picture. Spreadsheets of slot-ms totals are how people buy 500.

Admin project: reservations often live outside the scanned warehouse project. A scanner that is jobUser on the data project but cannot read the admin project's timeline will fail soft. Get the extra metadata permission or accept that underutilization cannot fire.

When not to buy more slots (and when not to shrink)

Buying slots to fix SELECT *, unpruned 4 TB reads, or a MERGE that rewrites 3 TB does not help, since those jobs will occupy any floor given to them. Fix the jobs first and watch the timeline change.

Shrinking when p95 is near assigned, even if mean utilization is 30%, just recreates Monday's problem on a smaller floor.

Shrinking a shared reservation from one project's timeline alone is risky; inventory assignments first.

Buying Plus because Standard's rate 'felt slow' is a misdiagnosis. Edition feature sets are not slot speed; more slots are more slots, regardless of edition.

Treating one on-demand PENDING afternoon as a reason to sign a 3-year commitment overreacts to a blip. Shared pools blip; reduce bytes and retry instead.

Setting autoscale_max to 2000 on a 50-slot baseline 'just in case' invites a surprise slot-hour bill. Cap autoscale to a number that is explainable to finance.

dbt BATCH vs. Looker INTERACTIVE vs. Sheets

dbt, Dataform, and Airflow: priority BATCH, ETL reservation, early morning. They can wait 10 minutes and should not run INTERACTIVE in the BI pool. Labels and job_id prefixes make this auditable.

Looker explores: INTERACTIVE, BI reservation, thin contract views. PDT builds are ETL work, so put them on BATCH; 15-minute explore refreshes on the ETL pool are how teams end up buying 200 extra slots that only exist at 9:05.

Sheets cannot run BATCH. Isolate or precompute instead. A Sheets-driven 500-slot spike is a process failure, not a capacity shortfall.

Human consoles: an on-demand sandbox project, not a prod assignment. Peak-hour ad-hoc is the metric to watch. If analysts need prod, give them a BI reservation with a hard autoscale cap.

A fair scheduler will not simulate three reservations. Three SLAs need three assignments; comments in SQL will not do it.

Week-one playbook

Day 1: plot 14 to 30 days of per-minute consumed vs. assigned vs. PENDING share. Note p95, mean, reservation_id, and assignments. No purchases, no shrinks.

Day 2: list the jobs in the pinned hours (stars, MERGEs, human INTERACTIVE) and open those tickets. Run the Editions vs. on-demand list compare on the same window.

Day 3: split BATCH ETL from INTERACTIVE BI if they share a pool, or move humans to on-demand. This is the highest-leverage day that needs no purchase order.

Day 4: only if mean and p95 are both well under assigned and contention did not fire, propose one 50-slot step down. If on-demand is losing badly after SQL tickets are filed, propose PAYG ETL instead of a 3-year Plus commitment.

Days 5 through 7: re-scan. Underutilized $/mo, contention, PENDING, and peak-hour share should all move. If you shrank and PENDING jumped, step back up one increment. If nothing was done to SQL and slots were bought anyway, the floor will still be the wrong shape next month.

  • Day 1: one chart (consumed, assigned, PENDING). No shopping.
  • Day 2: jobs in the pinned hours + list-price model compare.
  • Day 3: isolate BATCH vs INTERACTIVE vs humans.
  • Day 4: one 50-slot step or a PAYG ETL plan, not a 3-year CUD.
  • Days 5-7: re-scan; undo one step if PENDING spikes, not all the way back to launch week.

Takeaways

  • A slot is a unit of compute; a slot-hour is 3,600,000 slot-ms; Editions bills the hour, not the query's wall-clock time.
  • Usage is concurrent slots over time from JOBS_TIMELINE. Job-level total_slot_ms cannot show you an 8-minute spike that pinned the pool.
  • You pay the reservation baseline every hour. 500 Enterprise PAYG slots are $21,600/month idle or busy; 200 are $8,640. Steps are 50 slots, minimum 50.
  • Idle floor is waste and gets priced; a floor you are pinned to is contention; PENDING is waiting to start. Three different problems, not one 'slot score.'
  • Mean utilization lies when p95 is pinned. Shrink only when both mean and p95 are under assigned and contention is quiet.
  • INTERACTIVE Looker and BATCH dbt on one reservation is how 'plenty of slots on average' still has 40-second dashboards at 9:05.
  • Buying slots to fix SELECT *, MERGE rewrites, or notebooks does not help, and shrinking a shared pool from one project's timeline alone is a mistake.
  • SlashPeak prices underutilized baselines, compares on-demand vs Editions, fail-softs missing timelines, and keeps contention as an advisory so it is not mixed into the waste total.

Take control of your warehouse bill

Understand, reduce and govern your cloud data spend with SlashPeak — starting today.

  • 30-day free trial
  • No credit card
  • Read-only access
  • Set up in minutes