< blog
12 min read

How to Cut Snowflake Compute Costs Before Your Contract Renewal (A 90-Day Plan)

Illustration of a calendar next to a descending bar chart with shrinking coin stacks and a flag on the last bar, for cutting costs before renewal

TL;DR

  • Your next Snowflake commitment is usually argued from your recent run rate. Waste you leave in during the last 90 days is waste you will be asked to commit to again.
  • Day 90: find out where you stand against the current contract. Day 75: find where compute goes. Day 60 to 30: remove idle time, resize, and delete work nobody uses. Day 30 to 14: rebuild the forecast from the new run rate, not the old one.
  • A handful of queries on Snowflake’s own usage views cover most of it. They are below.
  • Overage, rollover and unused-capacity terms are set in your order form. Snowflake’s documentation leaves them to the contract, so read yours before you decide what to cut or commit.
  • Measure query duration before and after every change. A saving that slows the pipelines is a deferred bill, not a saving.

Why the last 90 days before renewal matter

A renewal is the one moment a year when a finance partner, a vendor and an engineering lead look at the same number. That number is almost always the run rate of the recent past. If 20 percent of that run rate is a warehouse that sits idle between jobs, the conversation prices the idle time in.

You cannot fix this after signing without renegotiating, and you cannot tell the difference between growth and waste unless you separate them before the meeting. So the job in the 90 days is to produce three numbers: what you spend now, what you would spend without the waste, and what you expect to add through real growth.

Step 1 (day 90): know where you stand against the contract

Snowflake exposes contract balances in the ORGANIZATION_USAGE schema. Two views matter here.

-- Contract dates and what each contract item is worth
select contract_number, contract_item, start_date, end_date, expiration_date, amount, currency
from snowflake.organization_usage.contract_items;

— Where the balances stand, newest first
select r.date, r.currency, r.capacity_balance, r.on_demand_consumption_balance, r.rollover_balance
from snowflake.organization_usage.remaining_balance_daily r
order by r.date desc
limit 30;

What to know before you trust the output:

  • CONTRACT_ITEMS shows only the active contract, with CONTRACT_ITEM set to capacity, additional capacity or free usage. Amounts are in currency, not credits.
  • REMAINING_BALANCE_DAILY carries end-of-day balances. ON_DEMAND_CONSUMPTION_BALANCE is a negative number until the invoice is paid, and data for a day can change until month close.
  • Both views can be read only by the primary (funding) organization. Customers who signed through a Snowflake reseller cannot read them, so ask the reseller for the same numbers.
  • Latency is up to 72 hours for the balance view and up to 24 hours for the contract view.

Now turn the balance into a run rate and a runway. This query averages the last 30 days of capacity drawdown and compares the days of capacity left with the days left in the contract.

with daily as (
select r.date as balance_date,
lag(r.capacity_balance) over (order by r.date) - r.capacity_balance as used_that_day
from snowflake.organization_usage.remaining_balance_daily r
where r.date >= dateadd(day, -31, current_date())
),
burn as (
select avg(used_that_day) as avg_daily_use
from daily
where used_that_day is not null and used_that_day >= 0
)
select b.avg_daily_use,
latest.capacity_balance,
latest.capacity_balance / nullif(b.avg_daily_use, 0) as days_of_capacity_left,
datediff(day, current_date(), c.contract_end) as days_to_contract_end
from burn b
cross join (select capacity_balance
from snowflake.organization_usage.remaining_balance_daily
order by date desc limit 1) latest
cross join (select max(end_date) as contract_end
from snowflake.organization_usage.contract_items
where lower(contract_item) like '%capacity%') c;

If days of capacity left is shorter than days to contract end, you are heading into overage. If it is much longer, you may finish the term with unused capacity. Which of those costs you money, and how rollover works, depends on your order form, not on the view. A day on which you bought additional capacity shows as a negative drawdown and is excluded above.

Step 2 (day 75): find where compute goes

Compute is usually the largest line. Rank warehouses by credits over the last 90 days and split each one into the part that ran queries and the part that did not.

select warehouse_name,
round(sum(credits_used_compute), 1) as compute_credits,
round(sum(credits_attributed_compute_queries), 1) as query_credits,
round(sum(credits_used_compute) - sum(credits_attributed_compute_queries), 1) as idle_credits
from snowflake.account_usage.warehouse_metering_history
where start_time >= dateadd(day, -90, current_timestamp())
group by warehouse_name
order by compute_credits desc;

CREDITS_ATTRIBUTED_COMPUTE_QUERIES leaves out warehouse idle time, so the gap between the two columns is the credits spent while nothing was running. It is NULL for Adaptive Warehouses, so those show no idle figure here. The view is hourly with a latency of up to three hours and keeps 365 days.

Read the result like this:

Pattern What it usually means Next step
High idle share on a large warehouse Auto-suspend too long, or a job that keeps the warehouse awake Step 3
Low idle share but high credits Real work on a warehouse that may be larger than the work needs Step 4
Many small warehouses, each with a few credits Sprawl: separate warehouses per team that each idle on their own Consolidate where isolation is not needed

Step 3 (day 60): remove idle time

Two facts from Snowflake’s documentation set the rules.

  • A warehouse created with SQL has AUTO_SUSPEND = 600 by default, which is 10 minutes of inactivity. Setting it to 0 or NULL means the warehouse never suspends.
  • Billing is per second with a 60-second minimum each time a warehouse starts. Stopping a warehouse before the first 60 seconds are over saves nothing.

Snowflake recommends a low auto-suspend value, for example 5 or 10 minutes or less, matched to the gaps in your workload. The trade-off is the cache: a suspended warehouse drops its cached data, so the next query can be slower.

alter warehouse reporting_wh set auto_suspend = 120;

Start with warehouses that serve scheduled jobs, where there is no user waiting on a cold cache. Leave warehouses that serve interactive dashboards for later and test them one at a time.

Step 4 (day 50): check whether the size fits the work

Size is the second lever, and the first one people get wrong in both directions. Snowflake’s guidance is to run the same queries against more than one size and match the warehouse to the size and complexity of the queries. Larger is not necessarily faster for small, simple queries. Spill and queueing tell you which warehouses are actually under pressure.

select warehouse_name,
warehouse_size,
count(*) as queries,
sum(iff(bytes_spilled_to_remote_storage > 0, 1, 0)) as queries_spilling_to_remote,
round(avg(queued_overload_time) / 1000, 1) as avg_queue_seconds
from snowflake.account_usage.query_history
where start_time >= dateadd(day, -30, current_timestamp())
and warehouse_name is not null
group by warehouse_name, warehouse_size
order by queries desc;

A warehouse with no spill, no queueing and a high idle share from Step 2 is a candidate for one size smaller. A warehouse that spills or queues is a candidate for a larger size or, if the problem is many queries at once rather than one heavy query, more clusters. Snowflake notes that resizing is not intended for concurrency problems and that multi-cluster warehouses are designed for them.

Change one warehouse at a time and write down the date, so Step 6 can compare like with like.

Step 5 (day 40): delete the work nobody uses

Compute is not the only line that compounds. Look at what else burns credits in the background.

select service_type, round(sum(credits_billed), 1) as credits_billed
from snowflake.account_usage.metering_daily_history
where usage_date >= dateadd(day, -90, current_date())
group by service_type
order by credits_billed desc;

Warehouse usage will be near the top. Look at the next entries. AUTO_CLUSTERING that is large relative to the tables it serves is worth a close look (see our guide to finding the tables behind auto-clustering costs). Serverless tasks, search optimization and materialized views each deserve the same question: does anyone still read what this feeds?

Tables that nobody queries are the other half. A table that has not been read in months still costs storage and may still be loaded by a scheduled job every night.

Step 6 (day 30 to 14): rebuild the forecast from the new run rate

Wait until the changes have run for at least two weeks, then re-run the Step 2 query for the same warehouses and compare credits per day before and after, not totals, because the windows differ in length. Then build the three numbers.

Number How to get it Why finance needs it
Run rate before Average daily credits in the 90 days, in currency What a straight-line renewal would price in
Run rate after Average daily credits since your changes, in currency What you spend once the waste is gone
Growth you can name New workloads, teams or customers you expect to onboard, each with an owner The only part of the increase you should be committing to

Take the third number to the renewal as a list of named workloads. Growth that nobody can name is the part to challenge.

Two cautions:

  • Do not size a commitment to a dip you cannot explain. If last month fell because a pipeline was paused, it will come back.
  • Check performance after each change. Compare 95th percentile query duration (TOTAL_ELAPSED_TIME in QUERY_HISTORY) for the affected warehouses before and after. If it moved, undo the change or address the cause before counting the saving.

How Seemore handles this

Seemore Data keeps this plan running instead of leaving it as a quarter-end exercise. Contract Utilization shows where you stand against your Snowflake contract right now, where your current usage is projecting you to land, whether that means overage and how much you would need to save to stay within contract. It also shows which Seemore savings can close that gap. Auto-Shutdown suspends idle warehouses, and SmartPulse works out the right size for each warehouse hour by hour from its real usage, so idle time and sizing do not depend on a monthly review. If your renewal is inside 90 days, book a demo.

Frequently asked questions

When should I start preparing for a Snowflake renewal?

About 90 days before the contract end date, which is enough to make changes, let them run for two weeks and still have clean numbers for the negotiation. Read END_DATE and EXPIRATION_DATE in CONTRACT_ITEMS for the dates on your account.

Will cutting spend before renewal lower the discount I get?

Snowflake’s documentation does not answer that. Commitment size, discount and rollover terms are set in your order form with Snowflake or your reseller. Ask your account team how a lower commitment would be priced before you decide on a number.

I signed through a reseller. Can I still run these queries?

The Step 2 to Step 5 queries read ACCOUNT_USAGE and work on any account you own. The Step 1 queries read ORGANIZATION_USAGE views that Snowflake documents as unavailable to customers who signed through a reseller. Ask the reseller for the contract balance instead.

Is growth or waste driving our increase?

Separate them by workload. Group WAREHOUSE_METERING_HISTORY by warehouse and compare the last 30 days with the 30 days before. A warehouse whose credits rose while its query count stayed flat points to waste. A warehouse whose query count rose with its credits points to growth.

How do I cut compute spend without slowing pipelines?

Start with idle time, which has no performance cost on scheduled warehouses. Change size one warehouse at a time, and compare 95th percentile query duration before and after each change.