Performance Tuning in Snowflake — A Strategic Guide

Performance in Snowflake isn’t just about speed—it’s the critical balance between query response time, cost efficiency, and user concurrency. An untuned environment doesn't just lead to slow dashboards; it results in frustrated users, oversized warehouses, and a runaway credit bill.
Mastering performance requires a systematic approach. In this chapter, we'll walk you through a proven tuning framework: first Diagnose the bottlenecks, then Optimize your data structures and queries, and finally Automate enhancements using Snowflake's advanced features.
Part 1: Diagnose — The Art of Finding the Bottleneck
You can't fix what you can't see. Before changing a single line of code, you must diagnose the problem. This process starts with identifying problematic queries and then diving deep into their execution plans.
Step 1: Find Problematic Queries with QUERY_HISTORY
Your first stop is the QUERY_HISTORY view. This allows you to hunt for queries that are slow, inefficient, or resource-intensive.
SELECT
query_id,
query_text,
user_name,
warehouse_name,
warehouse_size,
total_elapsed_time / 1000 AS duration_sec,
partitions_scanned,
partitions_total,
bytes_spilled_to_remote_storage
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE start_time >= DATEADD('day', -7, CURRENT_TIMESTAMP())
AND (duration_sec > 60 OR bytes_spilled_to_remote_storage > 0) -- Find slow queries OR queries that spilled
ORDER BY duration_sec DESC
LIMIT 50;
This query helps you identify candidates for tuning. Once you have a query_id, your next step is Snowflake's most powerful diagnostic tool: the Query Profile.
Step 2: Dig Deeper with the Query Profile
The Query Profile provides a visual, step-by-step breakdown of a query's execution. Look for these common red flags:
Scenario A: Remote Disk Spilling
What it is: The #1 performance killer. Snowflake ran out of memory in the warehouse and had to write intermediate data to remote storage (disk), which is dramatically slower.
How to spot it: A step in the Query Profile will have a "Bytes spilled to remote storage" metric.
Common Cause: A junior analyst runs a query joining two massive tables without a proper WHERE clause, generating a huge intermediate result set that overwhelms the M-sized warehouse.
The Fix: You have two options: temporarily use a larger warehouse with more memory, or optimize the query to process less data upfront.
Scenario B: The "Exploding" Join
What it is: A join that produces significantly more rows than its inputs. This is often caused by incorrect join keys or an unintentional Cartesian product.
How to spot it: Look for an arrow between two steps where the row count explodes (e.g., 100K rows -> 10M rows).
Common Cause: A BI tool auto-generates a query with a faulty join condition between two tables, causing every row from one table to join with many rows from another.
The Fix: Correct the join logic in the query by ensuring the join keys are unique and correctly specified.
Scenario C: Inefficient Pruning (Full Table Scan)
What it is: Snowflake is forced to scan every micro-partition in a table because it can't eliminate any based on the query's filters.
How to spot it: The "Partitions scanned" is equal or very close to "Partitions total" on a large table.
Common Cause: Querying a massive 10TB log table for a single customer_id where the table has no clustering key. Snowflake has to read all 10TB to find the records.
The Fix: This is a structural problem that query logic alone can't fix. It requires optimizing the table's physical design.
Part 2: Optimize — Applying the Manual Fixes
Once you've diagnosed the bottleneck, you can apply targeted optimizations.
a. Optimizing Table Design with Clustering
For large tables (typically 1TB+) suffering from poor pruning, a clustering key is the solution. It reorganizes data physically to co-locate similar records within the same micro-partitions.
Scenario: Let's fix the inefficient pruning on our 10TB log_events table. We find that most queries filter by event_date and customer_id.
-- Re-create the table with a clustering key
CREATE OR REPLACE TABLE log_events (
event_id STRING,
customer_id STRING,
event_timestamp TIMESTAMP_NTZ,
event_date DATE,
payload VARIANT
)
CLUSTER BY (event_date, customer_id); -- Order keys from low to high cardinality
To check the health of your clustered table, run:
SELECT SYSTEM$CLUSTERING_INFORMATION('log_events');
Pay close attention to average_overlap_depth. A high number means partitions still contain overlapping data. Your goal is to keep this as low as possible.
b. Optimizing Query Logic
If the bottleneck is in the query itself, you can refactor it for better performance.
Scenario: A query is spilling to disk because it joins a huge facts table before filtering.
The "Bad" Query (Filter Late):
SELECT f.product_id, c.customer_name
FROM huge_facts_table f
JOIN customers c ON f.customer_id = c.customer_id
WHERE f.event_date >= '2025-01-01'; -- Filter is applied after the massive join
The "Good" Query (Filter Early with a CTE):
WITH filtered_facts AS (
SELECT product_id, customer_id
FROM huge_facts_table
WHERE event_date >= '2025-01-01' -- Filter the huge table FIRST
)
SELECT ff.product_id, c.customer_name
FROM filtered_facts ff
JOIN customers c ON ff.customer_id = c.customer_id;
By filtering the largest table first, you dramatically reduce the amount of data that needs to be processed in the join, often eliminating disk spilling entirely.
Part 3: Automate — Letting Snowflake Take the Wheel
For certain workloads, you can spend credits intelligently on automated features to save even more on warehouse compute.
When to use... Materialized Views
Problem: You have a dashboard with a predictable, repeated, and expensive query (e.g., daily sales summary).
Solution: A Materialized View pre-computes and stores the aggregation. Subsequent queries hit the stored result and return in sub-seconds.
Scenario: An executive dashboard aggregates billions of rows.
CREATE MATERIALIZED VIEW daily_sales_summary AS SELECT sale_date, region, SUM(amount) as total_sales FROM sales GROUP BY sale_date, region;
Trade-off: You pay for storage and the background compute credits needed to keep the view fresh.
When to use... The Search Optimization Service (SOS)
Problem: You have very large tables that require fast "point-lookup" queries (like a needle-in-a-haystack search).
Solution: SOS builds and maintains a highly optimized data structure that makes these lookups nearly instant.
Scenario: A customer support team needs to look up order details by order_id or customer_email in a multi-terabyte table.
ALTER TABLE orders ADD SEARCH OPTIMIZATION ON EQUALITY(order_id, customer_email);
Trade-off: You pay for storage and the initial build/maintenance compute for the search access paths.
When to use... The Query Acceleration Service (QAS)
Problem: You have unpredictable, large analytical queries that sometimes cause bottlenecks on a shared warehouse.
Solution: QAS acts as an "elastic boost" by offloading parts of the query (like large scans or sorts) to shared serverless compute resources.
Scenario: A data science team runs ad-hoc exploratory queries that can vary wildly in complexity, occasionally overwhelming the DATA_SCIENCE_WH.
ALTER WAREHOUSE DATA_SCIENCE_WH SET ENABLE_QUERY_ACCELERATION = true;
Trade-off: You are billed on a per-CPU-second basis for the "borrowed" compute. It's an insurance policy against outlier queries.
The Performance Tuning Flywheel
Performance tuning is not a one-time project; it’s a continuous cycle. Diagnose a slow query in the Query Profile → Optimize the table design or refactor the query logic → Automate predictable workloads with Snowflake's advanced features → and then Monitor again. By applying this framework, you can build a Snowflake environment that is not only fast and responsive but also cost-efficient and scalable.
Subscribe to my newsletter
Read articles from Sriram Krishnan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Sriram Krishnan
Sriram Krishnan
Sharing lessons, tools & patterns to build scalable, modern data platforms —one post at a time.