Cost, Credits, and Resource Monitors
The credit model, cloud-services charges, warehouse metering, account usage views, statement timeouts, and the controls that stop surprise bills.
The warehouse post treated cost the way you first meet it: a meter that ticks while a warehouse runs, and two switches — size and auto-suspend — that decide how fast and how long. That’s the part you touch every day, and for a solo learner poking at sample data it’s very nearly the whole story. But it is not the whole bill. An account running real workloads gets an invoice with several line items, and compute is only the loudest one. There’s a separate charge for the coordination layer, a separate charge for the bytes you keep, a growing list of charges for background features that run on compute you never sized, and a data-transfer line that most people forget until it appears. This chapter is about governing all of that — seeing it, attributing it, and putting a hard ceiling under it before it surprises you. Sizing lives in the warehouse post; everything a warehouse isn’t lives here.
The bill has five meters, not one
Before any control makes sense, you need the shape of what you’re controlling. Everything Snowflake charges you for resolves into credits (for compute of every kind) plus a flat dollar rate for storage plus data transfer. Laid out:
- Virtual warehouse compute — credits burned by the warehouses you create and size. The big meter, and the one from the last post.
- Cloud services compute — credits for the layer that plans queries, manages metadata, holds the result cache, and enforces security. Metered separately, with a free allowance below.
- Storage — a flat dollar-per-terabyte-per-month rate on the compressed bytes you keep, including Time Travel and Fail-safe copies.
- Serverless features — credits for background work Snowflake runs on its own managed compute: Snowpipe, automatic clustering, materialized-view maintenance, search optimization, Snowpipe Streaming, serverless tasks, and more. Each is its own line.
- Data transfer — dollars for bytes leaving a region or cloud (egress). Ingress is free; cross-region and cross-cloud movement is not.
A credit’s dollar value depends on your edition, cloud, and region, so hold it fuzzy — a few dollars — and never hard-code a number. The governance job is the same regardless of the exact figure: know which meter is spinning, and cap the ones that can run away.
The cloud-services meter and its 10% freebie
Every query you run leans on cloud services — the always-on brain that parses SQL, prunes micro-partitions, checks your role’s grants, and serves the result cache. It runs on compute Snowflake manages, and it’s metered in credits at the same rate as warehouse compute. Left there, that would mean paying twice for every query. You don’t, because of one rule worth memorizing:
Cloud-services credits are only billed for the portion that exceeds 10% of that day’s virtual-warehouse credits.
So if your warehouses burned 100 credits today, the first 10 credits of cloud-services usage are free, and you’re billed only for cloud-services usage past that. For ordinary analytics — queries that do real scanning on real warehouses — cloud services almost always stays under the 10% line and costs nothing. The adjustment is computed daily.
Where it does bite is workloads that lean heavily on the brain and lightly on the warehouses: floods of tiny metadata-only queries, INFORMATION_SCHEMA introspection in a loop, SHOW/DESCRIBE hammering, thousands of single-row DMLs, or a BI tool issuing constant lightweight lookups. Those consume cloud services without proportional warehouse compute, so they can punch through the 10% allowance. You can watch for it directly:
SELECT
TO_DATE(start_time) AS day,
SUM(credits_used_compute) AS warehouse_credits,
SUM(credits_used_cloud_services) AS cloud_svc_credits,
ROUND(100 * cloud_svc_credits
/ NULLIF(warehouse_credits, 0), 1) AS cloud_svc_pct
FROM SNOWFLAKE.ACCOUNT_USAGE.WAREHOUSE_METERING_HISTORY
WHERE start_time >= DATEADD('day', -14, CURRENT_TIMESTAMP())
GROUP BY 1
ORDER BY 1;
Any day where cloud_svc_pct runs well above 10 is a day cloud services actually cost you, and the fix is upstream: batch the tiny statements, cache the introspection, stop the polling loop.
Storage: the slow, cheap meter
Storage is the meter that never spikes and never sleeps. Snowflake charges a flat rate per terabyte per month, computed on your average daily storage across the month, measured on the compressed size of the data — which is typically a large reduction over the raw bytes you loaded. It covers table data, staged files in internal stages, and — this is the part people forget — the historical copies that Time Travel and Fail-safe retain. A high-churn table with 90-day Time Travel can hold far more history than live data.
There are two ways to pay for it. On-demand storage is post-paid: you’re billed monthly for whatever you used, at the higher per-terabyte rate (in US commercial regions, on the order of ~$40/TB/month). Capacity storage means you pre-purchase a block of storage up front as part of a commitment, at a substantially lower effective rate (roughly ~$23/TB in the same regions). On-demand is the honest default for anyone still learning the shape of their data; capacity is a finance decision you make once you have a stable, predictable footprint.
You watch storage over time, not by the hour — it barely moves day to day:
SELECT
usage_date,
ROUND(storage_bytes / POWER(1024, 4), 3) AS table_tb,
ROUND(stage_bytes / POWER(1024, 4), 3) AS stage_tb,
ROUND(failsafe_bytes / POWER(1024, 4), 3) AS failsafe_tb
FROM SNOWFLAKE.ACCOUNT_USAGE.STORAGE_USAGE
ORDER BY usage_date DESC
LIMIT 30;
If failsafe_tb or Time-Travel history dwarfs your live tables, the lever isn’t storage pricing — it’s your retention settings and how much you churn TRUNCATE/INSERT on big tables.
Serverless features each bill on their own line
The newest and most easily-missed part of the bill is serverless compute. These are features where Snowflake spins up and sizes its own compute in the background — you never CREATE WAREHOUSE for them, so they don’t show up under the warehouse you were watching. Each bills in credits, at a per-feature rate, on its own line:
- Snowpipe — continuous file ingestion, billed per GB ingested (a flat credit rate; the old per-file + per-compute-second model is retired — see chapter 06).
- Snowpipe Streaming — low-latency row-level ingestion, billed on its own meter.
- Automatic clustering — the background reclustering of tables with a clustering key (see the micro-partitions post).
- Materialized-view maintenance — keeping a materialized view in sync as its base table changes.
- Search optimization — building and maintaining the search-access path for point lookups.
- Serverless tasks, replication, and a handful of others.
The reason to know these exist as separate meters is that they don’t answer to your warehouse settings at all. Turning on automatic clustering for a hot, high-churn table can quietly become one of your largest line items, and no AUTO_SUSPEND will touch it — because there’s no warehouse to suspend. METERING_DAILY_HISTORY is where you see them broken out by service type:
SELECT
TO_DATE(usage_date) AS day,
service_type,
SUM(credits_used) AS credits
FROM SNOWFLAKE.ACCOUNT_USAGE.METERING_DAILY_HISTORY
WHERE usage_date >= DATEADD('day', -14, CURRENT_DATE())
GROUP BY 1, 2
ORDER BY 1 DESC, credits DESC;
You’ll see WAREHOUSE_METERING, PIPE, AUTO_CLUSTERING, MATERIALIZED_VIEW, SEARCH_OPTIMIZATION, and friends as distinct rows. That per-service breakdown is the first place to look when the monthly number moved and no warehouse got bigger.
Resource monitors: the one hard guardrail
Everything above is visibility. Visibility tells you what happened; it doesn’t stop it. The single object that actually enforces a ceiling is the resource monitor, and every real account should have at least one before a production workload lands.
A resource monitor watches credit consumption against a quota over a repeating window, and fires triggers at percentage thresholds. Only an account administrator can create one:
USE ROLE ACCOUNTADMIN;
CREATE OR REPLACE RESOURCE MONITOR monthly_guardrail
WITH
CREDIT_QUOTA = 1000
FREQUENCY = MONTHLY
START_TIMESTAMP = IMMEDIATELY
TRIGGERS
ON 75 PERCENT DO NOTIFY
ON 90 PERCENT DO SUSPEND
ON 100 PERCENT DO SUSPEND_IMMEDIATE;
Read that top to bottom. CREDIT_QUOTA = 1000 is the budget in credits for one window. FREQUENCY = MONTHLY resets the counter each month; the other choices are DAILY, WEEKLY, YEARLY, and NEVER (a one-shot lifetime cap). START_TIMESTAMP = IMMEDIATELY anchors the first window to now — you can instead pass a timestamp string to start it on a schedule. Then the TRIGGERS: at 75% of quota it merely notifies, at 90% it suspends the warehouses it governs, and at 100% it suspends them immediately.
That last distinction is the gotcha that catches everyone:
DO SUSPENDstops the warehouse from accepting new queries but lets in-flight statements finish. Graceful. You don’t lose the three-hour job that was 95% done.DO SUSPEND_IMMEDIATEkills running statements on the spot and rolls them back. Brutal, but it means the meter stops now rather than after the current work drains.
The idiomatic pattern is exactly the one above: a soft SUSPEND a little before the ceiling to catch the bleak gently, and a hard SUSPEND_IMMEDIATE at the ceiling as the true circuit-breaker. And NOTIFY needs a receiver — notifications only reach account administrators who have enabled them in their Snowsight notification preferences, so a monitor with only NOTIFY triggers and nobody subscribed is a silent monitor.
Account-level versus per-warehouse
A monitor does nothing until it’s assigned, and there are two scopes. Assign one at the account level and its quota covers the credits of all warehouses together:
ALTER ACCOUNT SET RESOURCE_MONITOR = monthly_guardrail;
Or bind a monitor to a specific warehouse so a single noisy workload can’t blow the whole budget:
CREATE OR REPLACE RESOURCE MONITOR etl_wh_cap
WITH CREDIT_QUOTA = 200 FREQUENCY = DAILY START_TIMESTAMP = IMMEDIATELY
TRIGGERS ON 100 PERCENT DO SUSPEND_IMMEDIATE;
ALTER WAREHOUSE etl_wh SET RESOURCE_MONITOR = etl_wh_cap;
A warehouse can have one monitor assigned to it, and the account can have one account-level monitor; both apply at once, so a warehouse is capped by the lower of its own quota and the account’s. The usual layout is a generous account-level backstop plus tighter per-warehouse caps on the workloads you trust least.
Two limits to keep honest. First, resource monitors govern user-managed virtual warehouses only. They do not cap cloud-services usage and they do not cap serverless features — a runaway Snowpipe or automatic-clustering bill sails straight past every monitor you own, because there’s no warehouse for the trigger to suspend. Second, a monitor is a ceiling, not a scheduler: once it suspends a warehouse at 100%, that warehouse stays down until the next frequency window resets the counter (or you raise the quota / resume it manually). That’s the point — but know it before a Friday-night cap idles your Saturday loads.
Seeing where the money went: ACCOUNT_USAGE
The SNOWFLAKE.ACCOUNT_USAGE schema is your billing telescope — a set of account-wide views with about a year of retained history. Three carry most of the cost story:
WAREHOUSE_METERING_HISTORY— hourly credits per warehouse, split intocredits_used_computeandcredits_used_cloud_services(you already used it above).METERING_DAILY_HISTORY— daily credits per service type, which is where serverless features surface.STORAGE_USAGE— daily bytes for tables, stages, and Fail-safe.
Rank your warehouses by spend for the month:
SELECT
warehouse_name,
ROUND(SUM(credits_used), 1) AS credits_mtd
FROM SNOWFLAKE.ACCOUNT_USAGE.WAREHOUSE_METERING_HISTORY
WHERE start_time >= DATE_TRUNC('month', CURRENT_DATE())
GROUP BY warehouse_name
ORDER BY credits_mtd DESC;
The one caveat that trips up every newcomer: ACCOUNT_USAGE views are not real time. They carry a documented latency — commonly up to ~45 minutes for QUERY_HISTORY and up to ~3 hours for the metering and storage views. So if you run a query right now and immediately check WAREHOUSE_METERING_HISTORY, you won’t see it. That lag is fine for governance (you’re watching trends, not seconds) but wrong for “did that query I just ran cost anything.” For live numbers, use the INFORMATION_SCHEMA table functions instead — they’re per-database, low-latency, but retain far less history:
SELECT *
FROM TABLE(INFORMATION_SCHEMA.WAREHOUSE_METERING_HISTORY(
DATE_RANGE_START => DATEADD('hour', -1, CURRENT_TIMESTAMP())
));
The rule of thumb: ACCOUNT_USAGE for the audit (deep history, slightly stale); INFORMATION_SCHEMA functions for the pulse (fresh, shallow).
And if you run multiple accounts under one organization, there’s a level up: SNOWFLAKE.ORGANIZATION_USAGE, with views like WAREHOUSE_METERING_HISTORY, STORAGE_DAILY_HISTORY, and USAGE_IN_CURRENCY_DAILY — the last of which translates credits into actual dollars across every account, which no single-account view can do.
Attributing cost to a job with query tags
Ranking warehouses tells you which box spent the money. It doesn’t tell you which pipeline, dbt model, dashboard, or person did — and when ten workloads share etl_wh, “the warehouse cost $400” is an argument, not an answer. The tool that turns cost into an accountable signal is the query tag: a free-text label you stamp onto a session, which then rides along on every query in QUERY_HISTORY.
ALTER SESSION SET QUERY_TAG = 'dbt:model=fct_orders|run=nightly';
-- ...the job's queries run here...
ALTER SESSION SET QUERY_TAG = '';
Now query_tag is a column you can group by. Orchestrators exploit this heavily — dbt, for instance, can set a per-model query tag automatically, so every model’s queries are labelled without you touching the SQL. You can also default a tag at the user or warehouse level with the same SET QUERY_TAG on ALTER USER / ALTER WAREHOUSE.
The subtlety is that QUERY_HISTORY gives you timings and bytes, not a direct per-query credit charge — historically you approximated a query’s share of a warehouse’s credits from its execution time. Snowflake now closes that gap with ACCOUNT_USAGE.QUERY_ATTRIBUTION_HISTORY, which attributes actual credits to individual queries. Join it to the tag and you get spend by job:
SELECT
q.query_tag,
ROUND(SUM(a.credits_attributed_compute), 2) AS credits
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_ATTRIBUTION_HISTORY AS a
JOIN SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY AS q
ON a.query_id = q.query_id
WHERE q.start_time >= DATEADD('day', -7, CURRENT_TIMESTAMP())
AND q.query_tag <> ''
GROUP BY q.query_tag
ORDER BY credits DESC;
That query is the whole discipline in one statement: cost stops being a number attached to infrastructure and becomes a number attached to work — which is the only form of it a team can actually act on.
Editions and what each tier unlocks
Two accounts running the identical query can pay different amounts, because the edition sets the per-credit price and gates which features even exist. There are four tiers, each a superset of the one before:
- Standard — the full SQL warehouse: databases, warehouses, secure data sharing, and 1 day of Time Travel. The entry price per credit.
- Enterprise — adds the features a growing team reaches for: multi-cluster warehouses (scale-out concurrency), up to 90 days of Time Travel, materialized views, search optimization, and the column-level dynamic data masking and row-access policies. This is where most serious analytics accounts sit.
- Business Critical — adds stronger security and compliance: Tri-Secret Secure (your own key in the encryption hierarchy), private connectivity (PrivateLink), and support for regulated data (HIPAA, PCI DSS).
- VPS (Virtual Private Snowflake) — a completely isolated, dedicated Snowflake environment for the strictest requirements. The top of the ladder in both isolation and price.
The gating is real and enforced. Ask Standard for an Enterprise feature and the statement fails outright:
-- MIN_CLUSTER_COUNT/MAX_CLUSTER_COUNT > 1 requires Enterprise or above:
CREATE WAREHOUSE reporting_wh
WAREHOUSE_SIZE = 'MEDIUM'
MIN_CLUSTER_COUNT = 1
MAX_CLUSTER_COUNT = 3
SCALING_POLICY = 'STANDARD';
-- On Standard edition this errors; on Enterprise it creates a multi-cluster warehouse.
The governance takeaway is that “should we upgrade editions” is partly a cost question in itself: a higher tier raises the price of every credit you burn, so you weigh the flat premium against the features (or compliance) you actually need. Longer Time Travel, in particular, upgrades storage and raises the credit rate — a double cost that has to earn its keep.
The Cost Management UI
You don’t have to write every one of these queries by hand. Snowsight’s Admin » Cost Management section renders the same ACCOUNT_USAGE data as dashboards: an Account Overview trending total credits and dollars, a Consumption explorer that slices spend by warehouse, service type, and time, and a Budgets area. It’s the fastest way to answer “did the bill move, and where,” and a good habit to open weekly. Everything it shows is queryable underneath — the UI is a convenience over the views, not a separate source of truth, which is why the daily-spend chart it draws is just this:
SELECT usage_date, SUM(credits_used) AS credits
FROM SNOWFLAKE.ACCOUNT_USAGE.METERING_DAILY_HISTORY
WHERE usage_date >= DATEADD('day', -30, CURRENT_DATE())
GROUP BY usage_date
ORDER BY usage_date;
Budgets deserve a note because they complement resource monitors rather than duplicating them. A resource monitor is a hard stop on warehouse credits — it suspends. A budget is a soft, broad watcher: you set a spending target on a group of objects (warehouses, databases, even serverless features that monitors can’t see) and it notifies when the forecast trends over. Use budgets to get an early warning across the whole bill, including the serverless meters a monitor is blind to; use resource monitors when you need something to actually pull the plug.
Final thoughts
The mental shift this chapter asks for is the same one the warehouse post started, pushed one level up. There, cost stopped being a monthly surprise and became a design choice inside a CREATE WAREHOUSE. Here, the whole bill stops being a single number and becomes a system you can read: five meters you can name, views that tell you which one moved, tags that tell you whose work moved it, and a resource monitor that refuses to let any of it run past a line you drew in advance. None of it is cleanup work done after the invoice lands — it’s plumbing you install before the first production workload, the same way you’d put a breaker in a panel before wiring the house.
If you do only three things, do these. Put a resource monitor with a SUSPEND_IMMEDIATE trigger over your account so nothing can run away overnight. Tag your jobs so a credit always traces back to the work that spent it. And check METERING_DAILY_HISTORY by service type once a week, because the line item that eventually surprises you won’t be the warehouse you were watching — it’ll be a serverless feature quietly running on compute you never sized, past the one guardrail that couldn’t see it.
Comments