🔍
BigQueryのパーティション入門
パーティションとは?
BigQueryのテーブルを特定の条件で分割して管理する仕組み。
大量のデータから必要な部分だけを効率よく処理するために使われる。
パーティションを使うメリット
-
🔍 クエリ性能の向上
必要なデータだけを読み取ることで、クエリの処理が速くなる。 -
💰 コスト削減
スキャンされるデータ量が減るため、クエリの料金を抑えられる。 -
🧹 データ管理がしやすい
古いデータを自動で削除したり、期間ごとに整理したりしやすくなる。
パーティションの種類
1. 時間単位列によるパーティション分割
DATE
, DATETIME
, TIMESTAMP
型の列を使って、日・月・年ごとにパーティションを分ける。
向いているケース
- イベントやトランザクションが時系列で記録されているデータ
- 日付でクエリをフィルタすることが多い場合(例:
WHERE order_date BETWEEN '2023-01-01' AND '2023-01-31'
) - データを日次、月次、年次で集計・管理したいとき
- タイムスタンプ列がすでに存在し、それを活用したいとき
例:売上データを日付ごとに分割する
CREATE TABLE my_dataset.sales_data
PARTITION BY DATE(order_datetime) AS
SELECT * FROM source_table;
2. 取り込み時間によるパーティション分割
データが BigQuery に取り込まれたタイミングで、自動的にパーティションを分ける方式。
_PARTITIONTIME
という擬似列が使われる。取り込まれた日時をもとにパーティション化されるので、元のデータに日付や時刻の列がなくても使える。
向いているケース
- ログデータやストリーミングデータなど、取り込みタイミングで管理したい場合
- データ投入のタイミングを基準にフィルタしたい場合
例:ログデータのテーブルを取り込み時間でパーティション化する
CREATE TABLE my_dataset.log_data
PARTITION BY _PARTITIONTIME
AS SELECT * FROM raw_logs;
3. 整数範囲によるパーティション分割(Range Partitioning)
整数型の列を使って、指定した範囲ごとにパーティションを分ける方式。
RANGE_BUCKET
関数と GENERATE_ARRAY
を使って範囲を定義する。
たとえば、GENERATE_ARRAY(0, 1000, 100)
を使うと、
0〜99、100〜199、200〜299…というように、100刻みでパーティションが作られる。
向いているケース
- ユーザーID、スコア、年齢、地域コードなど、数値でグループ分けしたい場合
- 日付や時刻ではなく、数値のレンジでデータを分割したいとき
- クエリで数値範囲をフィルタすることが多い場合(例:
WHERE user_id BETWEEN 200 AND 299
)
例:ユーザーIDを100刻みでパーティション化する
CREATE TABLE my_dataset.user_data
PARTITION BY RANGE_BUCKET(user_id, GENERATE_ARRAY(0, 1000, 100))
AS SELECT * FROM source_table;
パーティションの効果を比較するための検証構成
データセット構成
Terraform
resource "google_bigquery_dataset" "my_dataset" {
dataset_id = "my_dataset"
location = var.region
default_table_expiration_ms = 7200000
delete_contents_on_destroy = true
lifecycle {
ignore_changes = [
default_partition_expiration_ms
]
}
}
# パーティションありテーブル
resource "google_bigquery_table" "partitioned_table" {
dataset_id = google_bigquery_dataset.my_dataset.dataset_id
table_id = "partitioned_table"
deletion_protection = false
schema = <<EOF
[
{
"name": "id",
"type": "STRING",
"mode": "NULLABLE"
},
{
"name": "name",
"type": "STRING",
"mode": "NULLABLE"
},
{
"name": "created_at",
"type": "TIMESTAMP",
"mode": "NULLABLE"
}
]
EOF
time_partitioning {
type = "DAY"
field = "created_at"
expiration_ms = 2592000000
}
}
# パーティションなしテーブル
resource "google_bigquery_table" "non_partitioned_table" {
dataset_id = google_bigquery_dataset.my_dataset.dataset_id
table_id = "non_partitioned_table"
deletion_protection = false
schema = <<EOF
[
{
"name": "id",
"type": "STRING",
"mode": "NULLABLE"
},
{
"name": "name",
"type": "STRING",
"mode": "NULLABLE"
},
{
"name": "created_at",
"type": "TIMESTAMP",
"mode": "NULLABLE"
}
]
EOF
}
ロードするデータ
- 900,000件 (30日分30,000件)
クエリ実行
パーティションあり
SELECT id, name, created_at
FROM `partner-nishiie-test.my_dataset.partitioned_table`
WHERE DATE(created_at) >= DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY)
ORDER BY created_at DESC
パーティションなし
SELECT id, name, created_at
FROM `partner-nishiie-test.my_dataset.non_partitioned_table`
WHERE DATE(created_at) >= DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY)
ORDER BY created_at DESC
検証結果
- 処理されるバイト数が減り、結果的に課金されるバイト数にも反映されています。
※ 課金されたバイト数の最小が10MB
処理されたバイト数 (MB) | 課金されるバイト数 (MB) | |
---|---|---|
パーティションあり | 7.57 | 10 |
パーティションなし | 30.71 | 31 |
まとめ
- パーティションを上手に使って、クエリを高速化、コストを削減することができる
Discussion