What "spilling" actually means

Every Snowflake warehouse has a fixed amount of memory per node, sized by the warehouse's size (an X-Small has less than a Large, which has less than a 2X-Large). Certain operations -- sorting a large result set, building a hash table for a join, a GROUP BY over high-cardinality columns, a window function over a huge partition -- need to hold working data in memory while they run. When that working set doesn't fit, Snowflake doesn't fail the query. It spills the overflow to disk instead, in a strict two-stage hierarchy: local disk first, and only if that's also insufficient, remote cloud storage next.

Local spill vs remote spill

Illustrative example -- based on Snowflake's documented spilling behavior, not a live query

Same operation, three memory outcomes

Click a scenario to see how the working set is handled, and how that shows up in QUERY_HISTORY and elapsed time.

Advertisement

What causes a query to spill

Large sorts / ORDER BY

Sorting a big result set -- especially without a LIMIT that lets Snowflake avoid materializing the whole thing -- needs to hold the data being ordered in memory.

Hash joins on large tables

Building the hash table for the smaller side of a join still needs memory proportional to that side's size after filtering -- a join that looks small in row count but has wide rows can still spill.

High-cardinality GROUP BY

Aggregating over a column (or combination of columns) with millions of distinct values means millions of in-flight groups, each needing memory to track.

Window functions over large partitions

A window function with a wide PARTITION BY needs to hold each partition's rows in memory to compute the result -- a small number of very large partitions is worse than many small ones.

How to fix it

The two levers are reducing the memory the operation needs, or giving the warehouse more memory to work with. Reducing memory needs means filtering earlier (push a WHERE clause before the join or aggregation, not after), narrowing selected columns so wide rows don't inflate the working set, and checking whether a window function's PARTITION BY can be made narrower. Giving the warehouse more memory means resizing it up one notch for that workload -- doubling the warehouse size roughly doubles available memory per node, which is often enough to move an operation from "spills to remote" to "spills to local only," or from "spills to local" to "fits entirely in memory."

When spill isn't the real story

If Query Acceleration Service is enabled on the warehouse, Snowflake writes a small amount of data to remote storage for every eligible query even when QAS isn't actually invoked to help it -- so a small, steady BYTES_SPILLED_TO_REMOTE_STORAGE value across many queries on a QAS-enabled warehouse isn't automatically a red flag. The signal worth acting on is a spill volume that's large relative to bytes scanned, paired with elapsed time that visibly tracks it.

Key takeaways

  • Spilling is a two-stage fallback: local disk first, remote cloud storage only if local disk isn't enough.
  • Remote spill is the one to worry about -- Snowflake's own documentation calls it "even worse performance" than local spill.
  • BYTES_SPILLED_TO_LOCAL_STORAGE and BYTES_SPILLED_TO_REMOTE_STORAGE in QUERY_HISTORY track the two stages separately -- check both, not just whether "spilling happened."
  • Fix it by reducing the operation's memory footprint (filter earlier, narrow columns, narrow PARTITION BY) or by resizing the warehouse up for that workload.

Find your worst offenders

The download below ranks your own account's queries by remote spill first (the expensive kind), then shows which warehouses spill chronically -- a warehouse that shows up there every day is a sizing decision to revisit, not a single query to hand-tune.

Download spill-diagnostics.sql
Real, ready-to-run queries for your own Snowflake account -- worst offenders, chronic spillers, and the QAS caveat.