はじめに
BigQueryでパーティション(日付など)とクラスタリング(特定カラム)を設定したテーブルに対してクエリを投げる際、WHERE句の書き方一つでスキャン量が桁違いに変わることがあります。
今回、テスト実施中に「WHERE句で絞り込んでいるはずなのに、なぜかフルスキャンになっている」という事象に遭遇したので、原因と対策をまとめます。
前提
対象テーブルはおおよそ以下のような設計でした。
- パーティション:日付カラムで日次パーティション
- クラスタリング:特定のキーカラム(例:店舗コードなど)
想定では、WHERE句で日付とキーカラムを条件指定すれば、該当するパーティション・クラスタブロックのみを読みに行き、スキャン量は小さく抑えられるはずでした。
問題現象
テスト実施中、あるクエリのスキャンバイト数を確認したところ、想定よりも大幅に大きい値になっていました。
具体的には、同じ条件で絞り込んでいるはずのクエリなのに、書き方の違いだけで以下のような差が出ていました。
- 修正前(CASTあり):約 60GB スキャン
- 修正後(CASTなし):約 40MB スキャン
1000倍以上の差です。金額・実行時間の両方に直結するため、看過できない問題でした。
原因
原因は、WHERE句の中でパーティション/クラスタリング対象のカラムに対して CAST(... AS STRING) のような型変換をかけていたことでした。
イメージとしては、以下のようなクエリです。
-- スキャン量が膨れ上がるパターン
SELECT *
FROM `project.dataset.table`
WHERE CAST(partition_date AS STRING) = '2026-07-01'
AND CAST(cluster_key AS STRING) = '1234'
-- 本来意図していたパターン
SELECT *
FROM `project.dataset.table`
WHERE partition_date = DATE('2026-07-01')
AND cluster_key = 1234
BigQueryのクエリオプティマイザは、パーティション・クラスタリングによるプルーニング(不要なブロックの読み飛ばし) を、対象カラムに対する式が「素直な比較」である場合にのみ適用できます。
対象カラムに CAST などの関数を挟んでしまうと、オプティマイザはその式の結果がどのパーティション/ブロックに対応するかを事前に判断できなくなり、プルーニングを諦めて全ブロックを読みに行く=フルスキャン という挙動になります。
これはBigQueryに限った話ではなく、パーティションプルーニングを持つ多くのDWH製品に共通する考え方ですが、実際に1000倍単位の差として体感すると、影響の大きさを実感しました。
検証方法
修正前後でクエリ実行前に確認できる「処理予定バイト数」を比較することで、CASTの有無による差分を確認できます。
- BigQuery Console:クエリエディタでクエリを入力すると、実行前に「This query will process X bytes」という見積もりが表示される
-
bq query --dry_runでも同様にスキャン量の見積もりが取得可能
型を揃えたクエリと揃えていないクエリを両方dry runし、見積もりバイト数を比較するだけで、プルーニングが効いているかどうかの当たりがつけられます。
対策
- WHERE句でパーティション/クラスタリング対象のカラムを条件指定する際は、カラムの型とリテラルの型を一致させる(CASTを挟まない)
- 上流のデータ型が不統一な場合、クエリ側でCASTして帳尻を合わせるのではなく、書き込み時点で型を揃えておくことが望ましい
- 既存のクエリ資産がある場合、パーティション/クラスタリング対象カラムに対する
CASTの有無を横断的にチェックする価値がある
まとめ
パーティション・クラスタリングを設定していても、WHERE句の書き方次第でその恩恵を受けられないことがあります。特に「型が合っていないのでとりあえずCASTする」という書き方は、動作上は問題なく見えるため気づきにくい落とし穴です。
BigQueryのパフォーマンス調査をする際は、実行計画やスキャン量だけでなく、「WHERE句の対象カラムに余計な関数を挟んでいないか」も確認observation項目に加えることをお勧めします。