Snowflake Performance Tuning and Caching

A query that runs in 2 seconds serves a dashboard user instantly. The same query taking 4 minutes frustrates everyone and burns credits unnecessarily. Query performance tuning is the discipline of identifying why a query is slow and applying the right fix. Snowflake provides detailed execution metadata — including three layers of caching — that makes diagnosing and fixing slow queries systematic rather than guesswork.

Snowflake's Three Caching Layers

Snowflake caches query results and data at three different levels. Each layer has a different scope, lifetime, and benefit. Understanding all three helps you predict query behaviour and design workloads that exploit caching naturally.

THREE CACHING LAYERS DIAGRAM
==============================

LAYER 1: RESULT CACHE (Cloud Services layer)
  Stores: Complete query result sets
  Scope:  Account-wide (all users share it)
  Expiry: 24 hours, or reset if underlying data changes
  Benefit: Identical repeated queries return in milliseconds, zero compute cost

LAYER 2: LOCAL DISK CACHE (Virtual Warehouse nodes)
  Stores: Compressed micro-partition files from storage
  Scope:  Per-warehouse (each warehouse has its own cache)
  Expiry: Cleared when warehouse suspends
  Benefit: Similar queries on same table skip storage reads, much faster

LAYER 3: METADATA CACHE (Cloud Services layer)
  Stores: Min/max values, row counts, null counts per micro-partition
  Scope:  Account-wide (always available)
  Expiry: Never (updated whenever data changes)
  Benefit: Enables partition pruning — query skips irrelevant partitions entirely

CACHE HIT PRIORITY ORDER (fastest to slowest):
  1. Result Cache hit   --> instant, 0 credits
  2. Local Disk hit     --> fast, warehouse must be running (credits used)
  3. Storage read       --> slowest, reads from S3/Azure/GCS

Layer 1: Result Cache in Detail

When you run a SELECT query, Snowflake saves the entire result set in the Result Cache associated with Cloud Services. If the same query runs again within 24 hours — by any user in the account — Snowflake returns the cached result directly. The virtual warehouse does not activate. Zero credits are consumed.

RESULT CACHE EXAMPLE
=====================

09:00 AM — User Alice runs:
  SELECT country, SUM(revenue) FROM SALES GROUP BY country ORDER BY 2 DESC;
  --> Warehouse runs query: takes 8 seconds, returns 50 rows
  --> Result cached in Cloud Services layer

09:15 AM — User Bob runs identical query:
  SELECT country, SUM(revenue) FROM SALES GROUP BY country ORDER BY 2 DESC;
  --> Cloud Services recognises identical SQL + same underlying data
  --> Returns 50 rows from cache in <100 milliseconds
  --> Warehouse never wakes up, zero credits charged

09:30 AM — New rows inserted into SALES table
  --> Snowflake invalidates the cached result immediately

09:31 AM — User Carol runs the same query:
  --> Cache miss (data changed), warehouse executes fresh query
  --> New result cached for next 24 hours

RESULT CACHE REQUIREMENTS (ALL must be true):
  - Identical SQL text (including whitespace and case)
  - Same database, schema, and table context
  - Querying user has SELECT permission on the table
  - Underlying data has not changed since cache was populated
  - No non-deterministic functions used (no CURRENT_TIMESTAMP(), RANDOM(), etc.)

Layer 2: Local Disk Cache in Detail

Each virtual warehouse node maintains a local disk cache (SSD storage) that holds copies of micro-partition files recently downloaded from cloud storage. When a query needs to read a micro-partition already in cache, the warehouse reads from local disk instead of going back to S3, Azure, or GCS. Local disk reads are 10–100x faster than reading from object storage.

LOCAL DISK CACHE BEHAVIOUR
============================

First run of query on fresh warehouse:
  Query scans ORDERS table (1TB, 500 micro-partitions)
  Warehouse downloads all 500 partitions from S3 → local cache
  Query time: 90 seconds

Second run of same query (warehouse still running):
  Warehouse reads 500 partitions from local SSD cache
  Query time: 8 seconds (11x faster — no storage I/O)

Warehouse suspended and resumed:
  Local cache cleared on suspension
  Next query re-downloads all partitions from storage
  Query time: 90 seconds again

STRATEGIES TO MAXIMISE LOCAL CACHE:
  - Keep warehouse running between related query runs (longer auto-suspend)
  - Dedicate one warehouse per team/workload (prevents cache eviction by others)
  - Schedule batch jobs on the same warehouse to benefit from cached partitions

Reading the Query Profile

The Query Profile in Snowsight is the most powerful tool for diagnosing slow queries. Open it by clicking any query in your Query History and selecting the Profile tab. The profile shows a visual execution plan broken into operator nodes, with timing and data volume statistics for each node.

QUERY PROFILE — KEY METRICS TO CHECK
======================================

METRIC                     WHAT IT TELLS YOU
------                     -----------------
Bytes Scanned              Total data read from storage + cache
Bytes Scanned from Cache   How much came from local disk cache (want this HIGH)
Partitions Scanned         How many micro-partitions the warehouse read
Partitions Total           Total micro-partitions in the scanned table(s)
Pruning Ratio              Partitions Scanned ÷ Partitions Total (want this LOW)
Rows Produced              Number of rows in the final result
Spillage to Disk           Memory overflow written to disk (want this ZERO)
Remote Spillage            Overflow written to cloud storage (serious problem)

HEALTHY QUERY INDICATORS:
  Bytes from Cache:  > 70% of bytes scanned
  Pruning Ratio:     < 20% (only 20% of partitions actually scanned)
  Spillage:          0 bytes

PROBLEM INDICATORS:
  Bytes from Cache:  < 20% (cache cold or wrong warehouse)
  Pruning Ratio:     100% (all partitions scanned — no pruning happening)
  Spillage:          Any non-zero value

Partition Pruning: The Most Impactful Optimisation

Snowflake stores metadata — the min and max value of every column in every micro-partition — in the Cloud Services layer. When you include a filter in your WHERE clause on a column where data is naturally ordered (like a date column), Snowflake uses that metadata to skip entire partitions that cannot contain matching rows. This is called partition pruning.

PARTITION PRUNING ILLUSTRATION
================================

TABLE: ORDERS — 1,000 micro-partitions, each holding ~100K rows

Partition metadata (simplified):
  Partition 1:   order_date MIN=2022-01-01  MAX=2022-03-31
  Partition 2:   order_date MIN=2022-04-01  MAX=2022-06-30
  ...
  Partition 800: order_date MIN=2024-04-01  MAX=2024-06-30
  Partition 801: order_date MIN=2024-07-01  MAX=2024-09-30
  ...

YOUR QUERY: WHERE order_date = '2024-05-15'
  Partitions 1-799:  MAX < 2024-05-15 → PRUNED (skipped)
  Partition 800:     MIN ≤ 2024-05-15 ≤ MAX → SCANNED
  Partitions 801+:   MIN > 2024-05-15 → PRUNED (skipped)

Result: 999 partitions skipped, only 1 scanned
  Query scans 0.1% of the table's data — extremely fast

POOR PRUNING QUERY:
  WHERE UPPER(product_name) = 'HEADPHONES'
  Snowflake cannot apply metadata to a function result → scans ALL partitions
  Fix: WHERE product_name = 'Headphones'  (remove the function from the column)

Rules for Good Pruning

WRITE THIS (good pruning)             NOT THIS (poor pruning)
----------------------------          -------------------------
WHERE order_date = '2024-01-01'       WHERE TO_CHAR(order_date,'YYYY-MM-DD')='2024-01-01'
WHERE amount > 1000                   WHERE amount * 1.1 > 1100
WHERE status = 'SHIPPED'              WHERE LOWER(status) = 'shipped'
WHERE country_code = 'US'            WHERE SUBSTR(country_code,1,2) = 'US'

Rule: Never apply functions to a filtered column in WHERE.
      Apply the function to the literal value instead.

Spillage to Disk: A Major Performance Killer

When a warehouse node does not have enough memory to hold intermediate query results (for example during a large sort or a join between two massive tables), Snowflake writes the overflow to local disk (local spillage) or to cloud storage (remote spillage). Both slow queries significantly. Remote spillage is especially severe — it can make a query 10–50x slower.

SPILLAGE PREVENTION STRATEGIES
================================

PROBLEM: Sort or join runs out of memory
SOLUTION 1: Resize warehouse to a larger size (more RAM per node)
SOLUTION 2: Reduce data before the sort/join using earlier WHERE filters
SOLUTION 3: Pre-aggregate before joining large tables

PROBLEM: DISTINCT on massive datasets causes spillage
SOLUTION: Use APPROXIMATE_COUNT_DISTINCT() if exact count not needed
          (returns within 2% accuracy, much less memory)

PROBLEM: GROUP BY on high-cardinality columns causes spillage
SOLUTION: Filter to a smaller date range or partition before grouping

Practical Performance Checklist

PERFORMANCE CHECK                     ACTION IF FAILING
-----------------                     -----------------
SELECT * used                         Replace with specific column list
No WHERE clause on large table        Add a date or status filter
Function wrapping a filtered column   Move function to the literal side
JOIN on VARCHAR columns               Consider using INT surrogate keys
Multiple large table JOINs            Filter each table with CTE before joining
High remote spillage                  Increase warehouse size one step
Low cache hit rate                    Keep warehouse running between queries
Long queue time                       Enable multi-cluster or add warehouse
Identical query running repeatedly    Check if result cache disabled; re-enable

Using EXPLAIN to Preview the Execution Plan

-- See the query plan without actually running the query
EXPLAIN
SELECT
    c.country,
    COUNT(DISTINCT o.customer_id)   AS unique_buyers,
    SUM(o.amount)                   AS total_revenue
FROM ORDERS o
JOIN CUSTOMERS c ON o.customer_id = c.customer_id
WHERE o.order_date >= '2024-01-01'
GROUP BY c.country
ORDER BY total_revenue DESC;

-- EXPLAIN output shows:
--   Operation type (TableScan, Join, Aggregate, Sort)
--   Which table each operation reads
--   Estimated row counts at each stage
--   Whether indexes or pruning will be applied

Automatic Query Optimisation Features

Snowflake applies several automatic optimisations that require no manual action from you. Knowing they exist helps you trust Snowflake's decisions and avoid manually overriding them incorrectly.

  • Automatic micro-partition pruning: Snowflake always evaluates partition metadata before scanning — you cannot disable this, and you want it always active
  • Automatic join reordering: Snowflake's query optimiser rearranges joins to process smaller tables first and reduce intermediate result sizes
  • Automatic result caching: Enabled by default for all queries; disable only for testing with ALTER SESSION SET USE_CACHED_RESULT = FALSE
  • Automatic columnar compression: Snowflake chooses optimal compression algorithms per column type — no manual compression settings needed
  • Search Optimisation Service: An optional paid feature that builds secondary indexes on high-cardinality columns for point lookup queries (WHERE customer_id = 12345)

Key Points

  • Snowflake has three caching layers: Result Cache (24-hour account-wide), Local Disk Cache (per-warehouse, lost on suspend), and Metadata Cache (permanent, used for pruning)
  • The Result Cache returns identical queries in milliseconds with zero compute cost — design dashboards to exploit this with consistent SQL
  • The Query Profile in Snowsight shows bytes scanned, cache hit rate, partition pruning ratio, and spillage — check these for every slow query
  • Partition pruning is the highest-impact optimisation: filter on natural ordering columns (dates, IDs) without wrapping them in functions
  • Any spillage to disk or remote storage indicates insufficient warehouse memory — increase warehouse size one step and retest
  • Never apply transformation functions to columns on the WHERE clause left-hand side — this defeats pruning; apply functions to the literal value instead

Leave a Comment

Your email address will not be published. Required fields are marked *