Warehouse Performance
The cheapest byte is the one you never scan
Warehouse performance is decided by scan reduction: partition pruning, clustering/sort order, columnar projection, and caching. Concurrency costs slots and credits, so the tuning loop is: measure scanned bytes, then re-model.
What you will learn
- Rank the levers: partition, cluster/sort, projection, caching
- Read EXPLAIN to see pruned vs scanned ranges
- Tune concurrency alongside cost
What is this?
Columnar warehouses make the number of bytes scanned the dominating factor in both time and cost. Performance engineering is mostly about reducing those bytes through partitioning by time, clustering/sorting by other predicate columns, projecting only needed columns, and exploiting caching — then scaling compute for the unavoidable scans.
Why do you need it?
- The same 5 levers apply across Snowflake, BigQuery, Redshift, and even DuckDB locally.
- Most 'slow warehouse queries' are 20% engine and 80% schema/query shape.
- Concurrency is a slot/credit budget — your performance knob is modeling, not just compute.
Mental model
Choosing the right page of a catalog from the index
Partition + cluster pruning
The catalog's index (metadata) tells you the page (partition) and the order of listings (cluster) lets you skip sections; reading fewer pages makes both speed and cost better.
How it works
- Partition pruning: metadata excludes whole partitions when a time/natural-key filter applies.
- Clustering/sort orders rows so block-level pruning trims ranges even within a partition.
- Projection: columnar stores scan only referenced columns — SELECT * defeats projection.
- Caching: identical results within the cache window skip re-scan; cache fills per query pattern.
- Concurrency: active queries share slots; contention shows as queued slots, not 'bigger queries needed'.
The scan-reduction lever stack.
Examples
Before and after model-aware queries
-- BAD: scans everythingSELECT * FROM fact_event WHERE provider = 'clickstream'; -- BETTER: partition pruning + projectionSELECT user_id, count(*) FROM fact_eventWHERE day = '2026-01-05' AND provider = 'clickstream'GROUP BY user_id; -- Explain shows the pruned ranges:EXPLAIN SELECT ... -- look for partition pruning steps / estimated bytesSame answer, tiny fraction of the bytes — the whole reduction stack was exercised.
In production
- A funnel dashboard always filters day + country, so the team partitions by day and clusters by country.
- A cost review flags any model whose 'bytes scanned / total table' ratio exceeds 5% on hot queries.
- An analyst habit of 'SELECT * LIMIT 1000' is replaced by explicit column lists to stop full-block scans.
Best practices
- Design filters around the partition column (usually a date) in every hot query.
- Cluster/sort by the next highest-cardinality predicate that shows up after the partition key.
- Ban SELECT * in BI and notebook layers; projection is free but only if you ask.
- Watch cache hit ratio; a pattern that never hits cache is paying for re-scan every time.
Common mistakes
- Filtering on a derived expression (DATE_TRUNC(day, ts)) that defeats partition pruning.
- Partitioning or clustering on low-cardinality attributes, then wondering why nothing prunes.
- Scaling compute (more slots) before fixing a 100x scan-reduction opportunity in the model.
Debugging tips
- Check the 'bytes scanned' metric in the query profile; a big number is the smoking gun for pruning failure.
- Inspect EXPLAIN for a full-table scan reference even when a date predicate is present.
- Profile an identical filtered query; if it returns to full cost, the cache expired or the filter changed.
Interview checkpoint
Hands-on lab
- Compare full-scan vs pruned query cost in DuckDB
- Observe the effect of partitioning/clustering on scanned rows
- Measure projection savings with EXPLAIN
Prove Scan Reduction Locally
35 min · intermediate
Quick quiz
Which habit most reduces warehouse query cost?
Filtering on DATE_TRUNC(day, ts) defeats partition pruning when the table is partitioned by ts (a day).