Best Practices for Partitioning and Clustering in BigQuery for Optimal Query Performance

Partition tables by the most frequently filtered date or timestamp column, then cluster by low-to-moderate cardinality columns used in WHERE, JOIN, or ORDER BY clauses to minimize bytes scanned and reduce query costs.

BigQuery is a columnar, distributed data warehouse that charges based on the amount of data scanned per query. The DataTalksClub/data-engineering-zoomcamp repository demonstrates how strategic table design using partitioning and clustering enables the query engine to prune irrelevant data segments, often reducing scanned bytes from gigabytes to megabytes.

Understanding BigQuery Storage Architecture

BigQuery stores data in a columnar format across distributed systems. When a query executes, the engine reads only the referenced columns and the specific storage partitions containing relevant rows. Without partitioning or clustering, queries scan entire tables even when filters would logically exclude most data. Designing tables to support partition pruning and clustering ensures the engine skips unnecessary data blocks, lowering latency and cost.

Partitioning Strategies in BigQuery

What Is Partitioning

Partitioning divides a table into discrete, independent segments stored separately based on a specified column. BigQuery supports time-unit partitioning (by date, hour, month, or year) and integer-range partitioning. Each partition acts as a logical unit that can be scanned or ignored as a whole.

Performance Impact of Partitioning

The performance gains can be dramatic. In 03-data-warehouse/big_query.sql, a query filtering on tpep_pickup_datetime against a non-partitioned table scans approximately 1.6 GB of data. The identical query against a table partitioned by DATE(tpep_pickup_datetime) scans only 106 MB—a 93% reduction in bytes processed.

Best Practices for Partition Columns

  • Partition on the most frequently filtered timestamp column, typically date or datetime fields like tpep_dropoff_datetime or tpep_pickup_datetime.
  • Use daily partitioning for standard time-series data; avoid overly granular partitioning (hourly) unless necessary, as too many small partitions increase metadata overhead.
  • Consider integer-range partitioning when working with high-cardinality numeric keys where dates are not appropriate, such as order IDs or user IDs segmented into ranges.

Clustering Strategies in BigQuery

What Is Clustering

Clustering sorts rows within each partition by one or more specified columns, storing related data physically close together on storage blocks. Unlike partitioning which organizes data into separate segments, clustering organizes data within segments. When queries filter or order on clustering columns, BigQuery reads only the relevant blocks rather than entire partitions.

Optimal Clustering Columns

According to the homework exercises in cohorts/2026/03-data-warehouse/homework.md, the optimal strategy when queries always filter on tpep_dropoff_datetime and order by VendorID is to partition by tpep_dropoff_datetime and cluster on VendorID.

Select clustering columns with these characteristics:

  • Moderate cardinality (hundreds to thousands of distinct values). Columns like VendorID, payment_type, or rate_code_id are ideal.
  • Frequently used in filters, joins, or aggregations. BigQuery leverages clustering for WHERE, JOIN, GROUP BY, and ORDER BY operations.
  • Avoid high-cardinality columns like unique UUIDs or row IDs. Clustering on these provides minimal benefit and increases storage costs because the data cannot be effectively co-located.

When to Combine with Partitioning

Always use clustering in conjunction with partitioning, not as a replacement. Partitioning eliminates entire date ranges, while clustering eliminates blocks within the remaining partitions. This two-level pruning maximizes performance.

Implementing Partitioning and Clustering in BigQuery

Creating a Date-Partitioned Table

To create a table partitioned by pickup date:

CREATE OR REPLACE TABLE `taxi-rides-ny.nytaxi.yellow_tripdata_partitioned`
PARTITION BY DATE(tpep_pickup_datetime) AS
SELECT *
FROM `taxi-rides-ny.nytaxi.external_yellow_tripdata`;

This example is implemented in 03-data-warehouse/big_query.sql (lines 28-33).

Creating a Partitioned and Clustered Table

To add clustering on VendorID to the partitioned table:

CREATE OR REPLACE TABLE `taxi-rides-ny.nytaxi.yellow_tripdata_partitioned_clustered`
PARTITION BY DATE(tpep_pickup_datetime)
CLUSTER BY VendorID AS
SELECT *
FROM `taxi-rides-ny.nytaxi.external_yellow_tripdata`;

This SQL is found in 03-data-warehouse/big_query.sql (lines 54-60).

Querying with Pruning Benefits

A query leveraging both optimizations scans significantly less data:

SELECT COUNT(*) AS trips
FROM `taxi-rides-ny.nytaxi.yellow_tripdata_partitioned_clustered`
WHERE DATE(tpep_pickup_datetime) BETWEEN '2019-06-01' AND '2020-12-31'
  AND VendorID = 1;

As shown in 03-data-warehouse/big_query.sql (lines 61-71), this query scans approximately 864 MB compared to 1.1 GB when using partitioning alone, demonstrating the cumulative benefit of clustering.

Inspecting Table Metadata

Verify partitioning and clustering configuration using the information schema:

-- List partitions and row counts
SELECT table_name, partition_id, total_rows
FROM `nytaxi.INFORMATION_SCHEMA.PARTITIONS`
WHERE table_name = 'yellow_tripdata_partitioned'
ORDER BY total_rows DESC;

-- Identify clustering columns
SELECT *
FROM `nytaxi.INFORMATION_SCHEMA.COLUMNS`
WHERE table_name = 'yellow_tripdata_partitioned_clustered'
  AND clustering_ordinal_position IS NOT NULL;

Advanced Optimization Techniques

Avoid over-partitioning tables into too many small segments, which increases metadata size and can degrade performance rather than improve it. For ingestion-time partitioned tables, filter on the _PARTITIONTIME pseudo-column to guarantee partition pruning.

Monitor query performance using the BigQuery UI's bytes processed metric. Compare scans against non-partitioned baselines to validate optimization impact.

Design clustering columns carefully, as changing them requires a complete table rewrite. For frequently computed aggregates, consider materialized views built on partitioned and clustered base tables to further reduce scan costs.

Refer to Google's official BigQuery documentation linked in 04-analytics-engineering/setup/cloud_setup.md for additional guidance on sharding keys, table expiration policies, and storage pricing models.

Summary

  • Partition tables by the timestamp column most commonly used in time-based filters to enable partition pruning.
  • Cluster by columns with moderate cardinality (hundreds to thousands of values) that appear in WHERE, JOIN, GROUP BY, or ORDER BY clauses.
  • Combine both techniques to create a two-level pruning system: partitions eliminate date ranges, clustering eliminates blocks within those ranges.
  • Monitor bytes scanned before and after optimization to verify performance gains using the BigQuery query validator and execution details.

Frequently Asked Questions

What is the difference between partitioning and clustering in BigQuery?

Partitioning divides a table into separate storage segments based on a single column (typically date), allowing queries to skip entire partitions when filtering on that column. Clustering sorts data within each partition by one or more columns, co-locating similar values so queries can skip specific blocks within a partition. Partitioning reduces the total addressable dataset size, while clustering reduces the amount of data read within the selected partitions.

Should I partition or cluster by a high-cardinality UUID column?

Neither. High-cardinality columns like UUIDs are poor choices for both partitioning and clustering. Partitioning requires time-unit or integer-range semantics and creates too many small partitions if using unique values. Clustering on unique IDs provides minimal data elimination because each value appears in many different blocks, and it increases storage costs. Use moderate-cardinality columns like VendorID or payment_type for clustering instead.

How do I verify that my queries are actually pruning partitions?

Check the query execution details in the BigQuery UI or use the bytes processed estimate before running the query. If partition pruning is working, the bytes processed should be significantly less than the total table size. You can also query INFORMATION_SCHEMA.PARTITIONS to see which specific partitions contain data, then compare against your query's date filters.

Can I add clustering to an existing BigQuery table?

No, you cannot alter clustering on an existing table. To add or change clustering columns, you must recreate the table using CREATE OR REPLACE TABLE with the new CLUSTER BY clause, or create a new clustered table via a query that selects from the original table. Plan clustering columns during initial table design to avoid costly rewrites.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →