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 & workloadSchedule & auto-suspend ruleHrs / dayCr / hrCredits / 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:

  1. Full-table rebuilds on high-frequency schedules. Running CREATE OR REPLACE TABLE on 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.
  2. 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.
  3. Views stacked five layers deep under live dashboards. When a complex financial join is materialised as a database VIEW rather 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.
  4. 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:

SQL diagnostic · Compute attribution by target tableWarehouse query log
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