Vertex Data

Snowflake Cost Optimization: Where the Credits Go

A practitioner’s guide to Snowflake cost optimization: attribute spend, fix warehouse settings, prune scans with clustering/MVs, and prevent regressions.

EP
Eric Provencio
5 min read

You opened the Snowflake bill and need it down this quarter without breaking SLAs. The playbook: attribute spend from native telemetry, fix warehouses that leak credits, reduce scans where it matters, and remove pipeline anti-patterns. Below is the SQL, config, and guardrails to optimize Snowflake costs with evidence—no vendor benchmarks required. You can run it in any Snowflake environment and see cost visibility by warehouse, user, role, and tag. If you want help landing changes faster, Vertex Data Consulting has built and tuned platforms at scale.

Make spend falsifiable: cost attribution with ACCOUNT_USAGE

Start with ground truth so your optimization efforts are defensible. Use ACCOUNT_USAGE to perform cost attribution across warehouses, users, roles, and query tags. Two primary views: SNOWFLAKE.ACCOUNT_USAGE.WAREHOUSE_METERING_HISTORY (credits by hour) and SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY (text, bytes scanned, timing). These are the canonical sources for credit consumption and the right place to discover cost drivers and optimization opportunities.

-- Credits by warehouse/day (baseline for charges)
select warehouse_name, date_trunc('day', start_time) as day,
       sum(credits_used) as credits
from snowflake.account_usage.warehouse_metering_history
where start_time >= dateadd('day', -14, current_timestamp())
group by 1,2
order by 2,1;
-- Allocate hourly credits to queries by execution share (cost allocation)
with meter as (
  select date_trunc('hour', start_time) as h, warehouse_name, sum(credits_used) as cr
  from snowflake.account_usage.warehouse_metering_history
  where start_time >= dateadd('day', -14, current_timestamp())
  group by 1,2
), q as (
  select date_trunc('hour', start_time) as h, warehouse_name, query_id,
         user_name, role_name, query_tag, execution_time/1000.0 as secs
  from snowflake.account_usage.query_history
  where start_time >= dateadd('day', -14, current_timestamp())
    and warehouse_name is not null
)
select q.h, q.warehouse_name, q.user_name, q.role_name, q.query_tag, q.query_id,
       q.secs / nullif(sum(q.secs) over (partition by q.h, q.warehouse_name),0) * m.cr as est_query_credits
from q join meter m using (h, warehouse_name);

Add tags so spend is attributable by application or dashboard, then sort by est_query_credits to find the biggest wins. This is the backbone of snowflake cost management and cost and performance trade-offs you can explain.

# dbt_project.yml
models:
  +query_tag: "dbt:{{ target.name }}"

Links: WAREHOUSE_METERING_HISTORY, QUERY_HISTORY.

Warehouse sizing, auto-suspend, multi-cluster, and idle-time waste

Most snowflake costs hide in idle time. Right-size first, fix auto-suspend, then consider multi-cluster only if queues prove it. Pick the smallest warehouse size that clears peak without sustained queues; watch avg(execution_time), queued_overload_time, and concurrency. If queues are minimal, scale down; if SLA-bound queues persist, try one size up before enabling multi-cluster. This is warehouse optimization, not guesswork.

SettingWhenRisk
XS–S single clusterMost ELT/devShort spikes may queue
M–L single clusterHeavy transformsIdle burn if suspend loose
Multi-cluster 1–3Bursty BIPay for idle extra clusters
-- Find warehouses with high credits but low executed work
with q as (
  select warehouse_name, date_trunc('hour', start_time) h,
         sum(execution_time) as exec_ms, sum(queued_overload_time) as q_ms
  from snowflake.account_usage.query_history
  where start_time >= dateadd('day', -7, current_timestamp())
  group by 1,2
), m as (
  select warehouse_name, date_trunc('hour', start_time) h, sum(credits_used) as cr
  from snowflake.account_usage.warehouse_metering_history
  where start_time >= dateadd('day', -7, current_timestamp())
  group by 1,2
)
select m.warehouse_name, h, cr,
       exec_ms/3600000.0 as exec_hours, q_ms/3600000.0 as queue_hours
from m left join q using(warehouse_name,h)
order by cr desc;

On every Snowflake warehouse, enforce auto_suspend 60–120s, auto_resume true, min_cluster_count 1, scaling_policy='STANDARD'. Loose suspend is the most common—and fixable—source of cost problems. These are snowflake cost optimization best practices and best practices for optimizing credit burn before touching schema.

Clustering keys, materialized views, and the cost of pruning

Two native features change scan volume: clustering keys and materialized views. Each can deliver real query optimization; each can backfire if refresh/maintenance exceeds the savings. Align clustering keys to query patterns (range filters and common joins). Validate with SYSTEM$CLUSTERING_INFORMATION and query profiles showing fewer micro-partitions scanned—your proxy for query performance and spend. For MVs, reserve them for hot, stable aggregates; on churn-heavy tables they generate refresh compute and cloud services credits.

-- Check if clustering is paying off
select system$clustering_information('SALES.FACT_ORDERS', '(ORDER_DATE, CUSTOMER_ID)');
-- Example MV (refresh cost must be measured)
create materialized view analytics.mv_daily_orders as
select order_date, count(*) as c
from sales.fact_orders
where order_date >= dateadd('day', -120, current_date())
group by 1;

Also pilot the Search Optimization Service for highly selective lookups (especially on semi-structured). Keep what measurably reduces scans. Think of these as snowflake cost optimization techniques within broader optimization strategies for schema and workload design, not silver bullets for every table.

The dbt-side causes of runaway compute (and how to fix them)

Many bills trace to pipeline shape, not the engine. Fix these first if you’re tuning at scale and want to optimize without regressions:

  • Full-refresh drift: block jobs from running --full-refresh except via change control.
  • Weak incrementals: add unique_key, tighten predicates, and avoid backfilling every run.
  • Threads vs. cores: don’t hammer a single warehouse with max threads; match threads to effective cores implied by size.
  • Materialization sprawl: avoid persisting massive intermediates that could be ephemeral.
-- models/orders.sql (incremental done right)
{{
  config(materialized='incremental', unique_key='order_id', incremental_strategy='merge')
}}
select * from {{ ref('stg_orders') }}
{% if is_incremental() %}
  where updated_at > (select coalesce(max(updated_at), '1900-01-01') from {{ this }})
{% endif %}

These changes reduce snowflake compute by cutting scanned bytes and write work. For deeper guidance on thread tuning, join fan-out, and predicate pushdown: dbt run slow? Why it happens and 7 fixes that work. If you want targeted PRs that reduce runtime and compute costs, see dbt Repo Performance and our dbt Cloud Migration service. Eliminate unnecessary Snowflake spend before adding more hardware.

Worked example: measure savings on your own account

Scenario: a BI warehouse runs 24/7 with morning bursts. You suspect idle burn and a few heavy scans. Steps for snowflake cost optimization you can trust:

  1. Baseline: pick 7 comparable weekdays before and after change windows.
  2. Change A: tighten auto_suspend from 10m to 90s; keep min_cluster_count=1; scaling_policy='STANDARD'.
  3. Change B: add clustering on ORDER_DATE to the 40M-row FACT_ORDERS powering your top dashboard.
  4. Measure: compare warehouse/day credits; then for the dashboard tag, compare allocated credits and bytes scanned per query.
-- Warehouse credits (before vs. after)
select date(start_time) as d, warehouse_name, sum(credits_used) as cr
from snowflake.account_usage.warehouse_metering_history
where start_time between :t0 and :t1
group by 1,2
order by 1,2;
-- Query-level: allocated credits and pruning
/* join your allocation CTE to QUERY_HISTORY over each window */

Keep Change A if credits drop without rising queue time; that’s immediate cost reduction. Keep Change B only if profiles show fewer micro-partitions and lower bytes for the same rows, with equal or better query performance. Together these can produce measurable cost savings using snowflake cost optimization strategies you can defend.

Guardrails: monitors, automation, policy, and reporting

Lock in wins and catch spikes early. Use Resource Monitors to cap runaway jobs and post alerts to Slack. Program defaults on creation in each dev/prod Snowflake environment. Track serverless/cloud services features (tasks, MVs, search optimization) separately from compute and storage cost so attribution is clear.

-- Hard stop after N credits this month
create or replace resource monitor rm_bi with
  credit_quota = 500,
  frequency = monthly,
  start_time = immediately
  triggers on 80 percent do notify,
           on 100 percent do suspend;
alter warehouse bi_wh set resource_monitor = rm_bi;
-- Enforce defaults for new warehouses (Python)
import snowflake.connector as sf
SQL = """
alter warehouse identifier(?) set auto_suspend=90, auto_resume=true,
  min_cluster_count=1, scaling_policy='STANDARD';
"""

Also monitor storage and data transfer in ORGANIZATION_USAGE so compute vs. non-compute cost drivers are clear. Roll these into a dashboard stakeholders trust. This is cost management discipline—build an effective Snowflake cost program, not one-off fixes. Need help wiring this into your data warehouse? See Business Health Reporting.

FAQ: pricing, tools, Gen2, automation, ROI

How does Snowflake pricing work?

Workloads consume credits on virtual warehouses plus some cloud services; storage is billed separately. Multiply measured credits by your contract rate. Converting an average workload into expected spend comes from WAREHOUSE_METERING_HISTORY and your run hours.

Can Snowflake cost optimization be automated?

Partly. Resource monitors, policy defaults, and weekly reports from ACCOUNT_USAGE can cover most cases; keep humans in the loop for SLA trade-offs and to optimize where it matters.

Are optimization tools safe and effective?

Most optimization tools use the same telemetry you have. They can speed triage and remediation. Vet scopes (do they read query text?), and measure ROI by before/after credits on impacted tags and warehouses.

How do I develop a cost reduction strategy?

Baseline credits and bytes, fix autosuspend and warehouse size, then prune scans with clustering/MVs where filters are selective. These snowflake cost optimization strategies are measurable and repeatable.

Gen2 Warehouses?

Gen2 improves elasticity and concurrency, but doesn’t erase idle burn. Re-test suspend and sizing after enabling; treat this like any other warehouse optimization check.

Clustering keys and query cost?

They reduce partitions scanned when keyed to real filters/joins. Always verify with profiles and bytes scanned; that’s snowflake query optimization grounded in evidence.


If you want a partner who’s done this at scale in the Snowflake Data Cloud, Vertex can audit, prioritize, and land PRs fast. For the best Snowflake cost outcome, start a project with Vertex.

About the author

Eric Provencio Analytics engineer who has built and run production data platforms for Disney, Hulu, Nike, Peloton, Gopuff, and Kaplan. Founded Vertex Data Consulting to do the deep work most data teams never find time for: dbt Cloud migrations, repo performance, Airflow reliability, and AI agents that actually touch the stack.

Keep reading

Start a conversation

Tell us what's slowing your data team down.

We maintain a small client roster on purpose. If we're the wrong fit, we'll say so — and usually we know somebody who isn't.

  • Replies within 2 business days
  • NDA before specifics
  • Fixed-scope first engagement, retainer if it works
What do you want help with? *

We reply within 2 business days if there's a fit. No newsletters, ever.