Home / Data Platform / Warehouses

Data PlatformTroubleshootingIntermediate

Snowflake credit spike: find the warehouse, query or serverless feature that burned it

Split a Snowflake credit spike by service, warehouse, query and serverless feature with ACCOUNT_USAGE views, stop the burn, and add guardrails so it doesn't repeat.

Deepak Bhardwaj6 min read

Applies toSnowflake (all editions; QUERY_ATTRIBUTION_HISTORY needs a current account), SnowSQL / Snowsight

Symptom

A resource monitor fired, finance sent a screenshot, or the daily credit chart in Snowsight has a step change. You need to know what consumed the credits, who ran it, and whether it is still running.

Work from the top down: service type, then warehouse, then query or serverless object. Every query below uses SNOWFLAKE.ACCOUNT_USAGE, so run it as ACCOUNTADMIN or a role granted the USAGE_VIEWER / GOVERNANCE_VIEWER database roles.

Note

ACCOUNT_USAGE views have latency (from about 45 minutes up to several hours depending on the view). For "is it burning right now?", use SHOW WAREHOUSES and the query history in Snowsight, not these views.

Step 1: Which service type?

SELECT usage_date,
       service_type,
       SUM(credits_used)                         AS credits_used,
       SUM(credits_adjustment_cloud_services)    AS cloud_services_adjustment,
       SUM(credits_billed)                       AS credits_billed
FROM   snowflake.account_usage.metering_daily_history
WHERE  usage_date >= DATEADD(day, -14, CURRENT_DATE())
GROUP  BY 1, 2
ORDER  BY 1 DESC, credits_billed DESC;
Service type that jumped Where to look next
WAREHOUSE_METERING Step 2 (warehouses), then Step 3 (queries)
AUTO_CLUSTERING Step 4, automatic clustering
PIPE / SNOWPIPE_STREAMING Step 4, Snowpipe
SERVERLESS_TASK Step 4, serverless tasks
MATERIALIZED_VIEW, SEARCH_OPTIMIZATION Step 4, background maintenance
Cloud services only Very high query counts, heavy metadata calls (SHOW, INFORMATION_SCHEMA), or BI tools polling

Service type names vary as Snowflake adds features; check the distinct values your account returns.

Cloud services credits are only billed above a daily threshold relative to warehouse compute (the credits_adjustment_cloud_services column shows the credit back). If credits_billed rose because cloud services crossed that line, the cause is query volume, not query size.

Step 2: Which warehouse, and when?

SELECT warehouse_name,
       DATE_TRUNC('hour', start_time)  AS hour,
       SUM(credits_used_compute)       AS compute_credits,
       SUM(credits_used_cloud_services) AS cloud_services_credits
FROM   snowflake.account_usage.warehouse_metering_history
WHERE  start_time >= DATEADD(day, -3, CURRENT_TIMESTAMP())
GROUP  BY 1, 2
ORDER  BY compute_credits DESC
LIMIT  50;

Look at the shape:

  • Flat, high usage around the clock on a warehouse that should be idle at night: something keeps it awake (a BI dashboard refresh, a monitoring probe, a scheduled task every few minutes).
  • One large block at a specific hour: a single heavy job or a backfill.
  • Usage doubled after a date: someone resized the warehouse or raised MAX_CLUSTER_COUNT.

Check for configuration changes:

SHOW WAREHOUSES;

SELECT query_text, user_name, role_name, start_time
FROM   snowflake.account_usage.query_history
WHERE  query_text ILIKE 'alter warehouse%'
  AND  start_time >= DATEADD(day, -14, CURRENT_TIMESTAMP())
ORDER  BY start_time DESC;

Compare size, auto_suspend, min_cluster_count, max_cluster_count and scaling_policy with what the warehouse was meant to have.

Step 3: Which queries and users?

QUERY_ATTRIBUTION_HISTORY assigns compute credits to individual queries (execution only, not idle time):

SELECT user_name,
       warehouse_name,
       query_parameterized_hash,
       COUNT(*)                                     AS runs,
       SUM(credits_attributed_compute)              AS credits,
       SUM(COALESCE(credits_used_query_acceleration, 0)) AS qas_credits,
       ANY_VALUE(query_id)                          AS sample_query_id
FROM   snowflake.account_usage.query_attribution_history
WHERE  start_time >= DATEADD(day, -3, CURRENT_TIMESTAMP())
GROUP  BY 1, 2, 3
ORDER  BY credits DESC
LIMIT  25;

Grouping by query_parameterized_hash turns "10,000 small dashboard queries" into one row, which is usually the real answer. Pull the text for the sample ID:

SELECT query_text, role_name, query_tag, total_elapsed_time/1000 AS seconds,
       bytes_scanned, partitions_scanned, partitions_total
FROM   snowflake.account_usage.query_history
WHERE  query_id = '<sample_query_id>';

Attributed credits will not add up to the warehouse total. The gap is idle time: the warehouse running with nothing to do before AUTO_SUSPEND kicks in. A large gap points at a long auto-suspend or frequent wake-ups.

What you find Likely cause Fix
One query, huge credits, partitions_scanned close to partitions_total Full scan, missing filter or bad join Fix the SQL; see slow queries
Thousands of runs of the same hash Dashboard or app polling Raise the BI refresh interval, use result cache, move to a small dedicated warehouse
Attributed credits far below warehouse credits Idle time Lower AUTO_SUSPEND
Many clusters running Multi-cluster scale-out under concurrency Lower MAX_CLUSTER_COUNT, use ECONOMY scaling policy
Ad-hoc user on an XL warehouse Wrong warehouse granted Restrict USAGE on large warehouses

Step 4: Serverless features

These run without a warehouse, so they never show in Step 2.

-- Automatic clustering
SELECT database_name, schema_name, table_name, SUM(credits_used) AS credits
FROM   snowflake.account_usage.automatic_clustering_history
WHERE  start_time >= DATEADD(day, -7, CURRENT_TIMESTAMP())
GROUP  BY 1, 2, 3 ORDER BY credits DESC LIMIT 20;

-- Snowpipe
SELECT pipe_name, SUM(credits_used) AS credits, SUM(files_inserted) AS files
FROM   snowflake.account_usage.pipe_usage_history
WHERE  start_time >= DATEADD(day, -7, CURRENT_TIMESTAMP())
GROUP  BY 1 ORDER BY credits DESC LIMIT 20;

-- Serverless tasks
SELECT database_name, schema_name, task_name, SUM(credits_used) AS credits
FROM   snowflake.account_usage.serverless_task_history
WHERE  start_time >= DATEADD(day, -7, CURRENT_TIMESTAMP())
GROUP  BY 1, 2, 3 ORDER BY credits DESC LIMIT 20;

-- Materialized view refresh
SELECT database_name, schema_name, table_name, SUM(credits_used) AS credits
FROM   snowflake.account_usage.materialized_view_refresh_history
WHERE  start_time >= DATEADD(day, -7, CURRENT_TIMESTAMP())
GROUP  BY 1, 2, 3 ORDER BY credits DESC LIMIT 20;

Typical causes: a clustering key added to a table with heavy updates (constant reclustering), Snowpipe receiving many tiny files (per-file overhead), or a task scheduled every minute.

Stop the burn

Warning

Suspending a warehouse, task or pipe stops whatever depends on it. Tell the owning team before you do it, and record what you changed so you can revert.

-- Cancel one runaway query
SELECT SYSTEM$CANCEL_QUERY('<query_id>');

-- Cancel everything on a warehouse and stop it
ALTER WAREHOUSE etl_wh ABORT ALL QUERIES;
ALTER WAREHOUSE etl_wh SUSPEND;

-- Put the size and scale-out back
ALTER WAREHOUSE bi_wh SET WAREHOUSE_SIZE = 'SMALL' MAX_CLUSTER_COUNT = 2 AUTO_SUSPEND = 60;

-- Pause serverless consumers
ALTER TABLE sales.public.orders SUSPEND RECLUSTER;
ALTER TASK  ops.public.refresh_every_minute SUSPEND;
ALTER PIPE  raw.public.events_pipe SET PIPE_EXECUTION_PAUSED = TRUE;

Prevent it next time

Put a resource monitor on every warehouse that can grow, with notify and suspend thresholds:

CREATE OR REPLACE RESOURCE MONITOR rm_bi_wh
  WITH CREDIT_QUOTA = 300            -- set from your own budget
       FREQUENCY = MONTHLY
       START_TIMESTAMP = IMMEDIATELY
  TRIGGERS ON 75 PERCENT DO NOTIFY
           ON 100 PERCENT DO SUSPEND
           ON 110 PERCENT DO SUSPEND_IMMEDIATE;

ALTER WAREHOUSE bi_wh SET RESOURCE_MONITOR = rm_bi_wh;

Caution

SUSPEND_IMMEDIATE cancels running queries. Use it as the hard stop above the normal SUSPEND threshold, not as the first trigger, on warehouses that run production loads.

Then add the cheap guardrails:

  • Cap query runtime: ALTER WAREHOUSE bi_wh SET STATEMENT_TIMEOUT_IN_SECONDS = 1800;
  • Keep AUTO_SUSPEND at 60 seconds for most warehouses; raise it only where the local cache clearly pays off.
  • Give each workload (ETL, BI, ad-hoc, data science) its own warehouse, so Step 2 answers "who" on its own.
  • Require QUERY_TAG from pipelines (ALTER SESSION SET QUERY_TAG = 'dbt:orders_daily';) so Step 3 can group by job.
  • Schedule the Step 1 and Step 3 queries as a weekly report and alert on day-over-day jumps.

For the account-level setup of warehouses, roles and monitors, see the Snowflake account baseline.