Modern warehouses store data in chunks — partitions, micro-partitions, blocks, segments. The chunk's metadata (min/max per column, row count) lets the engine skip entire chunks at query time. Your job in schema design is to choose what those chunks look like.
Partitioning vs clustering — quick definitions
Partitioning = physical separation into directories or chunks based on a column's value. Each unique value of the partition key becomes its own physical group. Queries filter on the partition key → engine reads only matching partitions.
Clustering = logical sorting within partitions (or across the whole table). Doesn't separate into directories but reorders rows so that filters on the cluster key can prune blocks.
Per-engine vocabulary
| Engine | Partitioning | Clustering |
|---|---|---|
| BigQuery | PARTITION BY date_column (or integer range) | CLUSTER BY col1, col2, ... |
| Snowflake | Micro-partitions (auto) — no manual partitioning | CLUSTER BY (col1, col2) |
| Redshift | DISTKEY (distribution across nodes) | SORTKEY (sort within node) |
| Databricks | PARTITIONED BY (Hive-style) | ZORDER BY |
All similar; details vary. Snowflake auto-partitions and gives you only clustering. BigQuery requires both choices. Redshift uses different terms (DISTKEY/SORTKEY) but the underlying ideas overlap.
How to choose partition key
The partition key should be:
- Used in WHERE clauses often — partition pruning only helps if you actually filter on it.
- Low-to-medium cardinality — too few partitions = no pruning benefit; too many = metadata overhead and small file problems.
- Stable — partition keys can't easily change after the fact.
Best partition key for analytics: usually event_date or event_month — almost every query has a date filter.
-- BigQuery
CREATE TABLE fct_orders (
order_id INT64,
order_date DATE,
customer_key INT64,
revenue NUMERIC
)
PARTITION BY order_date
CLUSTER BY customer_key, region;
Bad partition keys:
order_id— too high-cardinality, every row its own partition.region— too low-cardinality, only 5 partitions, prunes too little.customer_email— irrelevant to filters, never helps.
Cluster keys
Clustering keys should be:
- Used in WHERE or JOIN conditions.
- Of medium-to-high cardinality (sorting helps when there are many distinct values to seek).
- Stable (re-clustering is expensive).
Common cluster keys: customer_key, product_key, region. Pick 1-4 columns. Adding too many cluster keys defeats the purpose because the engine can't sort well across all of them.
Real-world rules of thumb
1) Always partition by date/time on event-like tables
Almost every analytics query filters by a recent date range. Date partitioning is free performance — for a 1B-row table partitioned by month, a "last 30 days" query reads 1/12 of the data.
2) Cluster by the most-filtered non-date column
If queries frequently filter by region or customer_segment, cluster on it. The engine sorts data so all rows for the same region sit together; filter pushdown reads only those blocks.
3) Don't cluster on too many columns
Each additional cluster key dilutes the sort effectiveness. 2-3 columns is usually the sweet spot. 6+ columns clustering = no real ordering benefit.
4) Respect the data skew
If 90% of your data lands on one partition (e.g., partition by country and 90% of users are in India), partitioning gives you no benefit. Pick a more balanced key.
5) Pre-aggregate when partitioning is insufficient
For dashboards that scan billions of rows even after partitioning, consider a daily pre-aggregation table:
CREATE TABLE daily_revenue_by_region AS
SELECT order_date, region, SUM(revenue) AS revenue
FROM fct_orders
GROUP BY order_date, region;
Querying the pre-agg is 1000× faster. Refresh nightly.
Common partitioning mistakes
- No partition key at all — query reads the full table every time. Default-on partitioning by date should be standard.
- Partitioning by a high-cardinality column —
partition by user_idcreates one partition per user. Metadata overhead destroys benefit. - Partitioning by a column never used in WHERE — pruning needs a filter to work.
- Frequent re-partitioning — partition choice is fundamental. Re-doing it is expensive; choose well up front.
- Forgetting to cluster — partitioning alone helps date filters; clustering helps everything else.
Migration / inheritance reality
Many warehouses you'll inherit are under-partitioned. The fix is incremental:
- Identify the slowest dashboard queries.
- For each, find the dominant filter — usually a date column.
- Re-create the fact table with
PARTITION BYon that column. - Validate dashboard performance.
- Repeat for the next worst offender.
Most teams do this fact-by-fact over a quarter rather than a big-bang refactor.
Takeaway
Partition by what's filtered most (usually date). Cluster by the next most-filtered columns (1-4 of them). Respect cardinality and skew. The right partition+cluster choice can make a 10-second query into a 100ms one.