How cloud compute is actually billed
Cloud data warehouses separate storage from compute. Storage is cheap—typically around €20 to €25 per compressed terabyte per month. Compute is where the money goes, and its billing rules punish chatty, uncoordinated schedules.
In Snowflake’s virtual warehouse model, compute is billed per second while a cluster is running, subject to a strict 60-second minimum charge every time a suspended warehouse starts or is resized upward, and credit consumption doubles with each cluster size (X-Small = 1 credit/hour, Small = 2, Medium = 4, Large = 8). In Google BigQuery’s on-demand model, queries are billed by bytes scanned (with a 10 MB minimum per referenced table), meaning a full scan of an unpartitioned multi-gigabyte table costs the same whether it returns one row or ten million.
Worked example: 2,688 vs 83 credits
Consider a typical mid-market analytics estate on Snowflake: 40 GB of compressed ERP and CRM tables and 120 transformation models. Compare two ways of scheduling the exact same models over a 30-day month using Snowflake’s published credit formulas:
| Configuration & workload | Schedule & auto-suspend rule | Hrs / day | Cr / hr | Credits / 30d |
|---|---|---|---|---|
| Pattern A · Naive default Full dbt build on Medium |
Runs every 15 min (96×/day); 4 min execution + 10 min idle auto-suspend tail = 14 min billed per run | 22.40 h | 4.0 | 2,688.0 |
| Pattern B · Engineered schedule 1. Hourly incremental models ( X-Small) |
Runs hourly (24×/day); 45s incremental merge + 60s auto-suspend tail = 1.75 min billed per run | 0.70 h | 1.0 | 21.0 |
2. Nightly full reconciliation (Small) |
Runs once at 02:00 (1×/day); 11 min full rebuild & audit tests + 60s tail = 12 min billed | 0.20 h | 2.0 | 12.0 |
3. Daytime BI queries (X-Small) |
40 active query bursts across the workday; 100 total active + 60s tail minutes/day | 1.67 h | 1.0 | 50.0 |
| Pattern B total | Same data, same 120 models, 60-second auto-suspend, incremental daytime merges | 2.57 h | — | 83.0 |
Arithmetic check: Pattern A bills 96 × 14 min = 1,344 min/day (22.40 h) × 4 credits/h × 30 days = 2,688.0 credits. Pattern B bills (0.70 × 1 + 0.20 × 2 + 1.6667 × 1) × 30 = 83.0 credits (−96.9%).
Notice why Pattern A is so expensive: when a 4-minute job runs every 15 minutes on a warehouse with a 10-minute auto-suspend timeout, the cluster sleeps for only one minute out of every fifteen. Over 60% of the bill pays for an empty cluster waiting to time out.
Four pipeline habits that burn compute
When we inspect a high cloud warehouse bill, the waste almost always concentrates in four places:
- Full-table rebuilds on high-frequency schedules. Running
CREATE OR REPLACE TABLEon historical ledger or event tables every 15 or 30 minutes re-scans years of unchanged rows. Convert append-only and transaction tables to incremental materialisations partitioned by date, and reserve full refreshes for a single nightly or weekend run. - Uncoordinated ingestion and BI polling keeping clusters awake. If Fivetran syncs at minute
:00, Airbyte at:12, dbt at:25, and a BI dashboard refreshes every 20 minutes, a shared warehouse never suspends. Align batch syncs to fire together on the hour and lower auto-suspend to 60 seconds. - Views stacked five layers deep under live dashboards. When a complex financial join is materialised as a database
VIEWrather than a table, every filter click in Power BI, Looker or Tableau re-executes the entire underlying SQL graph from raw staging. Materialise final reporting marts as physical tables. - Parsing raw JSON blobs on every downstream query. Extracting nested fields from webhook or API payloads inside reporting queries forces full column scans on every read. Flatten and type JSON columns once in the staging layer.
SQL audit: find the expensive models
Before changing any pipeline, attribute your compute spend by query pattern. The SQL query below aggregates execution time, bytes scanned and rebuild frequency across your query history to surface the exact tables driving the bill:
WITH classified_queries AS (
SELECT
warehouse_name,
REGEXP_EXTRACT(UPPER(query_text), 'TABLE\\s+([A-Z0-9_.]+)', 1) AS target_table,
execution_time_ms / 1000.0 AS exec_seconds,
bytes_scanned / POWER(1024, 3) AS gb_scanned,
CASE
WHEN UPPER(query_text) LIKE '%CREATE OR REPLACE TABLE%' THEN 'FULL_REBUILD'
WHEN UPPER(query_text) LIKE '%MERGE INTO%' THEN 'INCREMENTAL_MERGE'
ELSE 'READ_OR_OTHER'
END AS query_pattern
FROM warehouse_query_history
WHERE start_time >= CURRENT_TIMESTAMP - INTERVAL '30 days'
)
SELECT
warehouse_name,
target_table,
query_pattern,
COUNT(*) AS runs_30d,
ROUND(SUM(exec_seconds) / 3600, 2) AS active_compute_hours,
ROUND(SUM(gb_scanned), 2) AS total_gb_scanned
FROM classified_queries
WHERE target_table IS NOT NULL AND target_table <> ''
GROUP BY 1, 2, 3
ORDER BY active_compute_hours DESC
LIMIT 20;
When a single-node engine is enough
Distributed cloud warehouses were built for petabyte-scale workloads across hundreds of concurrent users. If your entire analytical estate is under 100 GB of ERP, finance and operations data with a dozen internal readers, you do not need a multi-cluster distributed engine running around the clock.
Running transformations in a single-node columnar engine such as DuckDB over Parquet or Iceberg files in object storage—or materialising clean summary tables into PostgreSQL—often executes faster than a small cloud warehouse cluster because there is no network shuffle overhead, while cutting monthly infrastructure cost to a single-digit fraction.
Sources & technical references
- Snowflake Documentation: Understanding Compute Cost (Virtual Warehouse Credit Usage) — per-second billing rules, the 60-second minimum charge on start/resume, and credit rates by warehouse size.
- Google Cloud Documentation: BigQuery Pricing & On-Demand Compute — per-byte scanning rules, 10 MB minimum per referenced table, and partition pruning behaviour.
- DuckDB Documentation: Why DuckDB — in-process vectorised columnar execution for analytical workloads on single-node hardware.