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 WAREHOUSESand 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_IMMEDIATEcancels running queries. Use it as the hard stop above the normalSUSPENDthreshold, 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_SUSPENDat 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_TAGfrom 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.