Table of Contents
- The Optimization Mindset for Snowflake
- The Three Caching Layers
- Partition Pruning — The Foundation of Performance
- Clustering Keys — When Natural Order Is Not Enough
- Search Optimization Service
- Materialized Views
- Query Profile — Diagnosing Slow Queries
- Warehouse Sizing and Scaling Strategy
- Disk Spillage — When Memory Runs Out
- SQL Optimization Patterns
- Cost Monitoring and Control
- The Optimization Checklist
- Common Mistakes
- Interview Questions
- Wrapping Up
In the previous post, we covered Iceberg tables, data sharing, and cloning. By now you know how to load, transform, and share data in Snowflake. But are your queries fast? Are you spending more credits than necessary? This post covers every optimization lever Snowflake gives you: caching layers that eliminate redundant work, clustering keys that organize data for faster scans, query profiling that pinpoints bottlenecks, warehouse sizing strategies that balance speed and cost, and monitoring queries that keep your bill under control.
Analogy — Tuning a car. A car (Snowflake) runs well out of the box, but optimization makes it faster and cheaper. Caching is like cruise control — the car remembers the route and avoids recalculating. Clustering keys are like sorting your garage so you find tools instantly instead of searching every drawer. Query profiling is the diagnostic scanner that tells you exactly what is slowing the engine. Warehouse sizing is choosing between a sedan (Small) and a truck (4X-Large) based on what you are hauling. And cost monitoring is the fuel gauge — ignore it and you run out of budget on the highway.
The Optimization Mindset for Snowflake
Optimization priority order (most impact first):
1. REDUCE DATA SCANNED -- Filter early, prune partitions, select fewer columns
2. LEVERAGE CACHING -- Same query twice? Free from cache
3. OPTIMIZE DATA LAYOUT -- Clustering keys align data with query patterns
4. RIGHT-SIZE WAREHOUSES -- Match compute to workload complexity
5. TUNE SQL -- Better joins, fewer subqueries, avoid SELECT *
The golden rule:
Fix the QUERY before scaling the WAREHOUSE.
A Large warehouse scanning 100% of partitions costs 8x more than
a Small warehouse scanning 1% of partitions.
Fix pruning first, scale second.The Three Caching Layers
Snowflake has three distinct caching layers. Understanding them is essential because they can make repeat queries effectively free.
Analogy — Three levels of memory. Result cache is like a Post-it note on your monitor — the exact answer to a question you asked before, instantly available. Warehouse cache (local disk cache) is like your desk drawer — recently used files are right there, no need to go to the filing cabinet. Remote disk is like the filing cabinet across the room — still accessible, but slower. Each layer is checked in order: Post-it first, desk drawer second, filing cabinet third.
Result Cache (Metadata Cache)
Result Cache:
- Stores the EXACT result of a previously executed query
- Valid for 24 hours (or until underlying data changes)
- FREE -- no warehouse needed, no credits consumed
- Shared across ALL users in the account (same query = same cache)
Requirements for cache hit:
- Exact same SQL text (whitespace and comments matter)
- Same role executing the query
- Underlying data has not changed since cache was created
- Query does not use non-deterministic functions (CURRENT_TIMESTAMP, RANDOM)
Example:
User A runs: SELECT region, SUM(amount) FROM orders GROUP BY region;
-> Query executes on warehouse, result cached, costs credits
User B runs the exact same query 10 minutes later:
-> Result returned from cache instantly, costs $0, no warehouse neededWarehouse Cache (Local Disk Cache)
Warehouse Cache:
- Each warehouse caches recently read micro-partitions on local SSD
- Persists as long as the warehouse is RUNNING (not suspended)
- When a query reads the same partitions again, they come from local SSD (faster)
- Cleared when warehouse suspends
Implication for auto-suspend:
Auto-suspend = 60 seconds -> cache cleared frequently (saves credits, slower repeat queries)
Auto-suspend = 300 seconds -> cache retained longer (costs more idle time, faster repeats)
Trade-off: cache retention vs idle cost
Best practice:
For BI dashboards (repeated queries): auto-suspend = 300-600 seconds
For development (one-off queries): auto-suspend = 60 seconds
For ETL (sequential jobs): auto-suspend = 120 secondsRemote Disk (Cloud Storage)
Remote Disk:
- When data is not in warehouse cache, Snowflake reads from cloud storage
- This is the slowest layer but still fast (parallel reads from S3/Blob/GCS)
- Optimization here is about reading LESS data (partition pruning, column pruning)Partition Pruning — The Foundation of Performance
Partition pruning is the single most impactful optimization in Snowflake. When a query has a WHERE clause, Snowflake checks micro-partition metadata (min/max values per column) and skips partitions that cannot contain matching rows.
-- This query prunes well (order_date aligns with natural data ordering)
SELECT * FROM orders WHERE order_date = '2026-07-19';
-- Snowflake checks metadata: which partitions have dates including July 19?
-- Scans maybe 5 out of 5,000 partitions = 0.1% of data
-- This query prunes poorly (customer_id is random across partitions)
SELECT * FROM orders WHERE customer_id = 12345;
-- customer_id values are scattered across all partitions
-- Every partition's min/max range includes 12345
-- Scans all 5,000 partitions = 100% of dataChecking Pruning Efficiency
-- Method 1: Query Profile in Snowsight
-- Run your query -> click Query ID -> Query Profile tab
-- Look at "Partitions scanned" vs "Partitions total"
-- Good: scanned << total (e.g., 50 of 5,000)
-- Bad: scanned ≈ total (e.g., 4,800 of 5,000)
-- Method 2: Query history view
SELECT
query_id,
query_text,
partitions_scanned,
partitions_total,
ROUND(partitions_scanned / NULLIF(partitions_total, 0) * 100, 1) AS pct_scanned,
bytes_scanned,
total_elapsed_time / 1000 AS seconds
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE start_time >= DATEADD('day', -1, CURRENT_TIMESTAMP())
AND partitions_total > 100
ORDER BY pct_scanned DESC
LIMIT 20;
-- Focus on queries where pct_scanned > 50% -- these need optimizationClustering Keys — When Natural Order Is Not Enough
Data in Snowflake is naturally clustered by insertion order. If you load orders by date, the data is naturally clustered by date — and queries filtering by date prune well. But if you frequently query by a column that does not align with insertion order (customer_id, region, product_category), you need explicit clustering.
Analogy — A bookshelf. Natural clustering is like placing books on a shelf in the order you buy them — finding all books by date is easy (they are sequential), but finding all books by a specific author requires scanning the entire shelf. A clustering key is like reorganizing the shelf by author — now finding books by author is instant, though finding by purchase date is slower. Choose the clustering key based on your most common search pattern.
-- Add a clustering key to a large table
ALTER TABLE orders CLUSTER BY (order_date);
-- Clustering key on multiple columns (most selective first)
ALTER TABLE orders CLUSTER BY (region, order_date);
-- Check clustering quality
SELECT SYSTEM$CLUSTERING_INFORMATION('orders', '(order_date)');
-- Returns: average_overlaps, average_depth, total_constant_partition_count
-- Lower overlaps and depth = better clustering
-- Monitor automatic clustering credits
SELECT * FROM SNOWFLAKE.ACCOUNT_USAGE.AUTOMATIC_CLUSTERING_HISTORY
WHERE TABLE_NAME = 'ORDERS'
AND START_TIME >= DATEADD('day', -7, CURRENT_TIMESTAMP())
ORDER BY START_TIME DESC;When to Cluster
Cluster a table when:
- Table has more than 1 TB of data (small tables do not benefit)
- Queries frequently filter on a specific column (WHERE region = ...)
- The query profile shows high partition scan ratios (>50%)
- The filtered column does not align with insertion order
Do NOT cluster when:
- Table is small (under 1 TB -- pruning works fine without clustering)
- Queries always do full table scans (no WHERE clause)
- Table has very low query frequency (clustering costs credits to maintain)
Choosing clustering columns:
- Most common WHERE clause columns
- Date/timestamp columns (almost always a good choice)
- Low-to-medium cardinality columns (region, status, category)
- NOT high cardinality columns (customer_id with millions of unique values)
- Maximum 3-4 columns in a clustering keySearch Optimization Service
Search Optimization Service (SOS) improves performance for point lookup queries (WHERE column = exact_value) and substring searches (LIKE ‘%pattern%’). It creates a secondary search structure that works alongside partition pruning.
-- Enable search optimization on a table (Enterprise edition)
ALTER TABLE customers ADD SEARCH OPTIMIZATION;
-- Enable on specific columns only (more targeted, lower cost)
ALTER TABLE customers ADD SEARCH OPTIMIZATION
ON EQUALITY(customer_id, email)
ON SUBSTRING(customer_name);
-- Check search optimization status
SHOW TABLES LIKE 'customers';
-- Look for "search_optimization" and "search_optimization_progress" columns
-- Monitor search optimization cost
SELECT * FROM SNOWFLAKE.ACCOUNT_USAGE.SEARCH_OPTIMIZATION_HISTORY
WHERE TABLE_NAME = 'CUSTOMERS'
ORDER BY START_TIME DESC;When to use Search Optimization:
Use when:
- Point lookups on high-cardinality columns (WHERE id = 12345)
- LIKE with leading wildcard (WHERE name LIKE '%smith%')
- IN clauses with many values
- Queries on columns with millions of unique values
- Columns not suitable for clustering (too many unique values)
Do NOT use when:
- Range queries (BETWEEN, >, <) -- clustering is better
- Low-cardinality columns -- clustering handles these well
- Small tables -- overhead not worth itMaterialized Views
Materialized views store precomputed query results and automatically refresh when source data changes. They are ideal for expensive aggregations that many users query frequently.
-- Create a materialized view
CREATE MATERIALIZED VIEW revenue_by_region AS
SELECT
region,
DATE_TRUNC('month', order_date) AS month,
COUNT(*) AS order_count,
SUM(amount) AS total_revenue,
AVG(amount) AS avg_order_value
FROM orders
GROUP BY region, DATE_TRUNC('month', order_date);
-- Query the materialized view (same as querying a table)
SELECT * FROM revenue_by_region WHERE region = 'Ontario';
-- Snowflake automatically refreshes the MV when source data changes
-- Refresh cost is incremental (only processes changed partitions)
-- Check materialized view refresh history
SELECT * FROM SNOWFLAKE.ACCOUNT_USAGE.MATERIALIZED_VIEW_REFRESH_HISTORY
WHERE MATERIALIZED_VIEW_NAME = 'REVENUE_BY_REGION'
ORDER BY START_TIME DESC;Materialized views vs Dynamic Tables:
Materialized Views:
- Limited SQL support (no joins, no UDFs, no subqueries)
- Automatic background refresh (no scheduling)
- Transparent to queries (optimizer routes to MV automatically)
- Best for: single-table aggregations queried by many users
Dynamic Tables:
- Full SQL support (joins, subqueries, complex transformations)
- Refresh based on TARGET_LAG schedule
- Queried directly by name
- Best for: multi-step transformations, medallion architectureQuery Profile — Diagnosing Slow Queries
The Query Profile is your diagnostic tool for understanding why a query is slow. Access it in Snowsight: run a query, click the Query ID, then click the Profile tab.
What to look for in the Query Profile:
1. PARTITION PRUNING
Look at: TableScan nodes
Check: "Partitions scanned" vs "Partitions total"
Fix: Add clustering key or adjust WHERE clause to enable pruning
2. DISK SPILLAGE
Look at: red warning indicators
Check: "Bytes spilled to local storage" and "Bytes spilled to remote storage"
Fix: Increase warehouse size (more memory) or reduce data volume
3. CARTESIAN JOINS (EXPLODING ROWS)
Look at: Join nodes with massive row output
Check: Output rows >> input rows
Fix: Add missing join conditions or filter before joining
4. REMOTE DISK I/O
Look at: High percentage of time in "Remote Disk I/O"
Check: Large data scans, poor pruning
Fix: Improve pruning, add clustering, use warehouse cache
5. NETWORK
Look at: Data transfer between nodes
Check: High data transfer in joins/aggregations
Fix: Filter earlier, reduce columns in SELECTProgrammatic Query Analysis
-- Find the most expensive queries (by credits)
SELECT
query_id,
LEFT(query_text, 100) AS query_preview,
warehouse_name,
warehouse_size,
ROUND(total_elapsed_time / 1000, 1) AS seconds,
partitions_scanned,
partitions_total,
bytes_scanned / POWER(1024, 3) AS gb_scanned,
bytes_spilled_to_local_storage / POWER(1024, 3) AS gb_spilled_local,
bytes_spilled_to_remote_storage / POWER(1024, 3) AS gb_spilled_remote
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE start_time >= DATEADD('day', -7, CURRENT_TIMESTAMP())
AND execution_status = 'SUCCESS'
AND total_elapsed_time > 60000 -- Over 1 minute
ORDER BY total_elapsed_time DESC
LIMIT 20;
-- Find queries with poor pruning
SELECT
query_id,
LEFT(query_text, 100),
partitions_scanned,
partitions_total,
ROUND(partitions_scanned / NULLIF(partitions_total, 0) * 100, 1) AS pct_scanned
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE start_time >= DATEADD('day', -7, CURRENT_TIMESTAMP())
AND partitions_total > 500
AND partitions_scanned / NULLIF(partitions_total, 0) > 0.5
ORDER BY partitions_scanned DESC
LIMIT 20;Warehouse Sizing and Scaling Strategy
Sizing decision matrix:
Query runs in 30s on Small, need it in 15s?
-> Scale UP to Medium (doubles compute, halves time for parallelizable work)
-> But check pruning first -- maybe fix the query instead
10 users queuing for the same warehouse?
-> Scale OUT with multi-cluster (Enterprise edition)
-> Adds clusters for concurrency, not single-query speed
ETL job runs for 2 hours on Medium?
-> Try Large -- if it finishes in 1 hour, same cost, faster delivery
-> Doubling size doubles speed for parallel workloads
Dashboard queries take 5s each, 50 users at 9 AM?
-> Multi-cluster warehouse: min=1, max=5, scaling_policy=STANDARD
-> Auto-adds clusters as users queue, removes when demand drops-- Analyze warehouse utilization over the past week
SELECT
warehouse_name,
COUNT(*) AS query_count,
ROUND(AVG(total_elapsed_time / 1000), 1) AS avg_seconds,
ROUND(MAX(total_elapsed_time / 1000), 1) AS max_seconds,
SUM(credits_used_cloud_services) AS cloud_credits
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE start_time >= DATEADD('day', -7, CURRENT_TIMESTAMP())
AND warehouse_name IS NOT NULL
GROUP BY warehouse_name
ORDER BY query_count DESC;
-- Find idle warehouse time (wasted credits)
SELECT
warehouse_name,
SUM(credits_used) AS total_credits,
SUM(credits_used_compute) AS compute_credits,
SUM(credits_used_cloud_services) AS service_credits
FROM SNOWFLAKE.ACCOUNT_USAGE.WAREHOUSE_METERING_HISTORY
WHERE start_time >= DATEADD('day', -30, CURRENT_TIMESTAMP())
GROUP BY warehouse_name
ORDER BY total_credits DESC;Disk Spillage — When Memory Runs Out
Spillage occurs when a query’s intermediate results exceed the warehouse’s available memory. Snowflake spills data first to local SSD (faster) then to remote storage (slower). Spillage is the primary cause of unexpectedly slow queries.
Spillage indicators:
- Query Profile shows red "Bytes Spilled to Local Storage" or "Bytes Spilled to Remote Storage"
- Query takes much longer than expected for its data volume
- Warehouse CPU is low but query is still running (waiting for disk I/O)
Fixes (in priority order):
1. REDUCE DATA VOLUME -- filter earlier, select fewer columns, pre-aggregate
2. BREAK INTO STEPS -- split large query into multiple CTEs or temp tables
3. SCALE UP WAREHOUSE -- more memory = less spillage (last resort because it costs more)
Important: scaling up is the LAST resort, not the first
A 4X-Large warehouse scanning bad data costs 128x more than an X-Small on good dataSQL Optimization Patterns
-- AVOID: SELECT * (reads all columns from all partitions)
SELECT * FROM orders WHERE order_date = '2026-07-19';
-- BETTER: Select only needed columns (columnar storage reads less data)
SELECT order_id, customer_name, amount FROM orders WHERE order_date = '2026-07-19';
-- AVOID: Functions on filter columns (prevents pruning)
SELECT * FROM orders WHERE YEAR(order_date) = 2026;
-- BETTER: Use range filter (enables pruning)
SELECT * FROM orders WHERE order_date >= '2026-01-01' AND order_date < '2027-01-01';
-- AVOID: Correlated subqueries (execute once per row)
SELECT * FROM orders o
WHERE amount > (SELECT AVG(amount) FROM orders WHERE region = o.region);
-- BETTER: CTE or window function (single pass)
WITH region_avg AS (
SELECT region, AVG(amount) AS avg_amount FROM orders GROUP BY region
)
SELECT o.* FROM orders o
JOIN region_avg r ON o.region = r.region
WHERE o.amount > r.avg_amount;
-- AVOID: Joining without conditions (cartesian join)
SELECT * FROM orders, customers;
-- ALWAYS: Explicit join conditions
SELECT * FROM orders o JOIN customers c ON o.customer_id = c.customer_id;
-- AVOID: DISTINCT on entire row (expensive sort)
SELECT DISTINCT * FROM orders;
-- BETTER: DISTINCT on specific columns, or GROUP BY
SELECT DISTINCT order_id, customer_name FROM orders;Cost Monitoring and Control
-- Daily credit consumption by warehouse (past 30 days)
SELECT
DATE_TRUNC('day', start_time) AS day,
warehouse_name,
SUM(credits_used) AS credits
FROM SNOWFLAKE.ACCOUNT_USAGE.WAREHOUSE_METERING_HISTORY
WHERE start_time >= DATEADD('day', -30, CURRENT_TIMESTAMP())
GROUP BY day, warehouse_name
ORDER BY day DESC, credits DESC;
-- Total monthly cost breakdown
SELECT
'Compute' AS category,
SUM(credits_used) AS credits,
SUM(credits_used) * 3 AS est_cost_usd -- ~$3/credit for Enterprise
FROM SNOWFLAKE.ACCOUNT_USAGE.WAREHOUSE_METERING_HISTORY
WHERE start_time >= DATE_TRUNC('month', CURRENT_DATE())
UNION ALL
SELECT
'Storage',
NULL,
AVG(storage_bytes + stage_bytes + failsafe_bytes) / POWER(1024, 4) * 23 -- ~$23/TB
FROM SNOWFLAKE.ACCOUNT_USAGE.STORAGE_USAGE
WHERE usage_date >= DATE_TRUNC('month', CURRENT_DATE());
-- Most expensive queries (top credit consumers)
SELECT
user_name,
warehouse_name,
LEFT(query_text, 80) AS query,
total_elapsed_time / 1000 AS seconds,
credits_used_cloud_services AS credits
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE start_time >= DATEADD('day', -7, CURRENT_TIMESTAMP())
ORDER BY total_elapsed_time DESC
LIMIT 10;The Optimization Checklist
Before scaling up a warehouse, check these in order:
[ ] Are you selecting only the columns you need? (no SELECT *)
[ ] Does the WHERE clause align with clustering/insertion order?
[ ] Is partition pruning effective? (check Query Profile)
[ ] Is the result cache being used? (same query, same role)
[ ] Are there disk spills? (check Query Profile for spillage)
[ ] Are joins correct? (no cartesian joins, no missing conditions)
[ ] Can the query be broken into smaller steps? (CTEs, temp tables)
[ ] Is the warehouse right-sized for the workload type?
[ ] Is auto-suspend set appropriately?
[ ] Is a resource monitor in place to catch runaway costs?
Only after checking all 10: consider scaling up the warehouse.Common Mistakes
Scaling up the warehouse to fix a bad query. A Large warehouse running a query that scans 100% of partitions costs 8x more than a Small warehouse running an optimized query that scans 1%. Always check partition pruning, clustering, and SQL patterns before increasing warehouse size.
Ignoring the result cache. If analysts run the same dashboard query 50 times a day, the result cache makes 49 of those queries free. But adding CURRENT_TIMESTAMP() to the SELECT list invalidates the cache every time. Remove non-deterministic functions from cacheable queries.
Clustering small tables. Clustering has ongoing maintenance costs (automatic reclustering credits). Tables under 1 TB rarely benefit because partition pruning already works well on naturally ordered data. Only cluster large, heavily queried tables where pruning is poor.
Using SELECT * in production queries. Snowflake uses columnar storage — each column is stored separately. Selecting all 50 columns when you need 3 means reading 17x more data. Always specify only the columns you need.
Setting auto-suspend too high for idle warehouses. A Medium warehouse idling for 10 extra minutes per session across 20 daily sessions wastes about 27 credits per month ($80 on Enterprise). Set aggressive auto-suspend for development (60s) and adjust for workload patterns.
Not using resource monitors. A runaway query, a misconfigured task, or a developer leaving a Large warehouse running over the weekend can burn hundreds of dollars. Every account should have at least one resource monitor with SUSPEND triggers.
Applying functions to filter columns.
WHERE YEAR(order_date) = 2026prevents partition pruning because Snowflake cannot evaluate the function against micro-partition metadata. UseWHERE order_date >= '2026-01-01' AND order_date < '2027-01-01'instead — pruning works perfectly.Not monitoring credit consumption regularly. Snowflake bills are typically 70-85% compute. Without weekly monitoring of per-warehouse credit usage, costs creep up unnoticed. Query WAREHOUSE_METERING_HISTORY weekly and set up resource monitor alerts.
Interview Questions
Q: What are the three caching layers in Snowflake and how do they work? A: Result cache stores exact query results for 24 hours — if the same query runs again (same SQL, same role, unchanged data), the result is returned instantly with zero compute cost. Warehouse cache (local disk cache) stores recently read micro-partitions on the warehouse’s local SSD — repeat reads of the same partitions are faster. Remote disk is the default layer where Snowflake reads micro-partitions from cloud object storage. Each layer is checked in order: result cache first, then warehouse cache, then remote storage. Result cache is free. Warehouse cache depends on the warehouse staying active (not suspended).
Q: What is partition pruning and how do you improve it? A: Partition pruning is Snowflake’s mechanism for skipping micro-partitions that cannot contain rows matching the query’s WHERE clause. Snowflake checks each partition’s metadata (min/max values per column) and only reads partitions that might contain matching rows. To improve pruning, filter on columns that align with data insertion order (typically date/timestamp), avoid applying functions to filter columns, and add clustering keys on columns that are frequently filtered but not aligned with insertion order. Check the Query Profile for the partition scanned vs total ratio.
Q: When should you add a clustering key to a table? A: Add a clustering key when the table is large (over 1 TB), queries frequently filter on a specific column that does not align with natural insertion order, and the Query Profile shows high partition scan ratios (over 50%). Choose columns that appear most often in WHERE clauses, preferring date columns and low-to-medium cardinality columns. Do not cluster small tables, tables with no WHERE clauses, or rarely queried tables, as clustering has ongoing maintenance costs (automatic reclustering credits).
Q: How do you diagnose a slow query in Snowflake? A: Use the Query Profile in Snowsight. Check partition pruning ratio first — if the query scans most partitions, fix the WHERE clause or add a clustering key. Check for disk spillage — bytes spilled to local or remote storage means the warehouse needs more memory or the query needs to process less data. Check for cartesian joins where output rows far exceed input rows. Check for high remote disk I/O which indicates poor pruning. Query ACCOUNT_USAGE.QUERY_HISTORY programmatically to find patterns across many queries. Fix the query before scaling the warehouse.
Q: What is the difference between scaling up and scaling out in Snowflake? A: Scaling up means increasing warehouse size (Small to Large) — this gives individual queries more compute nodes and memory, making complex queries faster. Scaling out means adding clusters to a multi-cluster warehouse (Enterprise edition) — this handles more concurrent queries without queueing. Scale up when single queries are slow due to complexity or data volume. Scale out when many users are waiting in queue for the same warehouse. Scaling up doubles the cost per hour. Scaling out adds clusters only when concurrency demands it and removes them when load drops.
Q: How do you control costs in Snowflake? A: Five key strategies. First, set aggressive auto-suspend on all warehouses (60-120 seconds for dev, 300 seconds for production). Second, set up resource monitors with NOTIFY and SUSPEND triggers at 75% and 100% of credit quotas. Third, right-size warehouses by analyzing query history — do not use Large when Small handles the workload. Fourth, optimize queries to reduce partition scans, avoid SELECT *, and use result caching. Fifth, query WAREHOUSE_METERING_HISTORY weekly to identify warehouses consuming unexpected credits. Compute is typically 70-85% of the Snowflake bill, so warehouse management is the primary cost lever.
Q: What are materialized views and when should you use them? A: Materialized views store precomputed query results that Snowflake automatically refreshes when source data changes. They are ideal for expensive aggregations that many users query frequently — Snowflake transparently routes queries to the materialized view when the optimizer determines it would be faster. Use them for single-table aggregations accessed by BI dashboards. Do not use them for complex multi-table joins (use Dynamic Tables instead) or rarely queried aggregations (the maintenance cost is not justified).
Wrapping Up
Snowflake optimization follows a clear priority order: reduce data scanned (pruning and column selection), leverage caching (result and warehouse cache), optimize data layout (clustering keys), right-size warehouses (match compute to workload), and tune SQL (better joins, fewer subqueries). The Query Profile is your diagnostic tool — check it before every warehouse scale-up. Resource monitors are your safety net — set them before you forget. And weekly credit monitoring keeps costs predictable.
With this post, we have covered the complete Snowflake data engineering stack: architecture, setup, loading, transformations, Snowpark, Iceberg, sharing, and optimization. You now have the knowledge to build, optimize, and maintain production-grade Snowflake environments.
Related posts: – Snowflake Overview & Architecture – Snowflake Account Setup & RBAC – Snowflake Loading Data & Snowpipe – Snowflake Transformations & CDC – Fabric Optimization Guide