BigQueryのパーティションとクラスタリングについての解説

記事タイトルとURLをコピーする

G-genの杉村です。

Google Cloud (GCP) の誇るデータウェアハウスサービスである BigQuery には、パフォーマンスの向上に当たり パーティションクラスタリング という重要な概念があります。
それぞれの仕組みや使い分けを解説していきます。

パーティション

パーティションとは

パーティション とは、 BigQuery の一つのテーブルを、特定の列の値を基準にして内部的に複数パートに分割する機能です。
これによりクエリ時にスキャンする範囲を狭め、パフォーマンス向上とスキャン料金の節約ができます。

分割基準として使う列をテーブル作成時に指定することで、パーティション分割されたテーブルを作成することができます。
パーティション列は一つしか指定できません。

テーブル作成方法は以下のドキュメントの通りです。

例として、以下のような DDL で作成することができます。

CREATE TABLE
    mydataset.purchase_tran (purchase_dt DATE, prod_id STRING, prod_name STRING, store_id INT64, store_name STRING)
PARTITION BY
    purchase_dt

f:id:ggen-sugimura:20211106135930p:plain
パーティションで分割されたテーブル

このように作成されたテーブルで以下のようにクエリを実行すると、 BigQuery は当該の値を含んだパーティションだけをスキャンします。

SELECT
    *
FROM
    mydataset.purchase_tran
WHERE
    purchase_dt = "2021-10-01"

パーティションが無ければ BigQuery はテーブル全体をフルスキャンしますので、それに比べて大幅にスキャン範囲を節約でき、料金と時間の節約となります。

また副次的な効果ですが、ユーザーが大規模なテーブル全体に対して誤ってクエリを実行した場合の費用の急増を防ぐ効果もあります。

パーティションの分割基準

時間の列

TIMESTAMP 型 / DATE 型 / DATETIME 型 のいずれかの列をパーティション列として指定可能です。

TIMESTAMP 列と DATETIME 列では、パーティションを時間単位、日単位、月単位、年単位のいずれかで作成できます。
DATE 列の場合、パーティションは日単位、月単位、年単位で作成できます。
※いずれもデフォルトは日単位

(DDL の例)

CREATE TABLE
    mydataset.newtable (transaction_id INT64, transaction_date DATE)
PARTITION BY
    transaction_date

取り込み時間

BigQuery がデータを取り込んだタイムスタンプに基づいてテーブルが分割されます。
分割粒度は、時間単位、日単位、月単位、年単位から選択できます。
※デフォルトは日単位

テーブル作成時には _PARTITIONTIME という疑似列 (仮想列) をパーティション列として指定します。

(DDL の例)

CREATE TABLE
    mydataset.newtable (transaction_id INT64)
PARTITION BY
    _PARTITIONDATE

整数範囲の列

分割基準列として INTEGER 型の列を指定可能です。
また分割の開始値・終了値と分割の間隔を指定できます。

(DDL の例)

CREATE TABLE
    mydataset.newtable (customer_id INT64, date1 DATE)
PARTITION BY
    RANGE_BUCKET(customer_id, GENERATE_ARRAY(0, 100, 10))

この例では customer_id 列でパーティショニングし、開始値 0、終了値 100、間隔 10 としています。
このようにしたとき customer_id が 0 から 9 の行が最初の パーティションに入り、 10 から 19 が次のパーティションに入ります。この処理が 99 まで続きます。
この範囲外の値は、__UNPARTITIONED__ という名前のパーティションに入ります。
customer_id が NULL の行は __NULL__ という名前のパーティションに入ります。

※ テーブルにどんなパーティションが存在しているかはテーブルのメタデータから取得できる他、 bq コマンドを使って パーティションの内容を取得 できます。

パーティションの管理

時間単位または取り込み時間で分割したテーブルの場合、パーティションの 有効期限 を設定できます。

指定した有効期限が過ぎたらデータは自動的に削除され、このとき BigQuery のユーザーに割り当てれたリソースは消費されません。
うまく使えば、ハウスキーピングのジョブをユーザーが作成する必要がなくなります。

なおテーブル単位で有効期限が設定されている場合は、テーブルの有効期限が優先されます。

パーティションの有効期限はテーブル作成時に指定するほか、作成後にも変更できます。

クラスタリング

クラスタリングとは

クラスタリング とは、 BigQuery のテーブルの特定の列の値に基づいてテーブルのデータをソート・内部的に近い位置に配置しすることでフィルタや集計のクエリを高速化する機能です。
テーブル作成時に特定の列をクラスタ化列として指定します。

クラスタリングを利用すると指定した列の値に基づいて行がソートされるため、 WHERE 句でこの列に基づいてフィルタするクエリを投げた際、不要なデータのスキャンをスキップすることができます。
またクラスタ化に指定した列で GROUP BY して集計するクエリの場合、行がソートされ近い位置に配置されているのでパフォーマンスが向上します。

クラスタリングはパーティションと併用することも可能です。
クラスタリングとパーティショニングを併用すると、データはパーティション分割された後にクラスタ化されます。

またクラスタ化列は複数 (最大 4 列) まで指定可能です。
複数指定した場合、指定の順番が重要になります。まず最初に指定した列で行がソートされ、次にその中で2番めに指定した列でソート、次に3番目... というようになります。

f:id:ggen-sugimura:20211106142327p:plain
クラスタ化されたテーブル

クラスタリングされたテーブルを作成する方法は、以下のドキュメントのとおりです。

例として以下のような DDL でクラスタリングされたテーブルを作成できます。

CREATE TABLE
    mydataset.purchase_tran_cls (purchase_dt DATE, prod_id STRING, prod_name STRING, store_id INT64, store_name STRING)
PARTITION BY
    purchase_dt
CLUSTER BY
   prod_id

また既存テーブルをクラスタリングすることも可能ですが、 仕様に注意 する必要があります。

クラスタ化に指定する列

クラスタ化に指定する列は、一意の値を大量に含む (カーディナリティの高い) 列が推奨されます。
そのほうが、ソートによるスキャン範囲のスキップの効果が高く期待されるためです。

また組み合わせて使われることの多い複数の列をクラスタ化すると効果が期待できます。
先の記述の通り、順番に注意して WHERE で指定あるいは GROUP BY される複数列をクラスタ化列として指定すると、効果が大きいでしょう。

参考1: BigQuery 特集: ストレージの概要
参考2: BigQuery のクラスタリングで メンテナンスの手間を省いて クエリを高速化

自動再クラスタリング

クラスタリングのメンテナンスは自動で行われます。
データが新規で追加されたり、変更されたりした場合でも、自動でクラスタリングが行われます。
一般的なデータウェアハウスで必要とされる VACUUM といった処理は不要です。

スロットなどのリソースが消費されることもなく、自動的・透過的に行われるため、ユーザーが意識する必要はありません。

パーティション VS クラスタリング

パーティションとクラスタリングの違い

パーティションとクラスタリングは併用できますが、どういうケースでどちらを使えばいいのか、どういう列をどちらに指定すればいいのか、使い分けに迷うときもあるかもしれません。

まず、パーティションとクラスタリングの違いは、以下のような点にあります。

  • パーティショニングでは実際のクエリ実行前に dry-run でスキャン量の試算ができる (料金試算が可能) 。クラスタリングでは、試算はテーブル単位/パーティション単位で行われ、実際のスキャン量は見積もりより小さくなる可能性がある。
  • パーティショニングでは 有効期限 の設定ができる。
  • パーティショニングでは分割粒度 (時間・日・月・年・整数範囲) の選択ができる。
  • パーティショニングでは1つの列しか指定できない。クラスタリングでは4列まで指定できる。
  • パーティショニングでは特定の型の列しか指定できないが、クラスタリングには型の制限はない。

パーティションとクラスタリングの使い分け

まずはパーティションを適用できる列があるかどうかを検討します。
以下のような場合、パーティションの利用を検討します。

  • 日付/時間列があり、日付/時間でフィルタするクエリがある
  • パーティションの有効期限設定を使いたい
  • dry-run でスキャン量 (費用) 見積もりを厳密に行いたい

パーティショニングの検討をした後、以下のような場合にクラスタリングを採用します。

  • 複数列に対してフィルタ/集計するクエリがある
  • カーディナリティが大きく一意の列がある
  • パーティションを使うと分割粒度が小さくなりすぎ、1テーブルあたりの上限である 4,000 パーティションを超えてしまう
  • テーブル内の大部分のパーティションが頻繁に (たとえば、数分ごとに) 変更されるミューテーション オペレーションが生成される場合、パーティションは避けてクラスタリングを利用する (上限 があるため)

多くの場合で、パーティションとクラスタリングを併用することでスキャン料を節減し、パフォーマンスとコスト効率を向上させることができるはずです。

参考1: パーティショニングとクラスタリング
参考2: クラスタリングを使用する場合
参考3: パーティション分割テーブルのクラスタリング

その他

注意点

1テーブルが持てるパーティション数には上限があり、1テーブルにつき4,000パーティションまでです。
これは、時間単位であれば 4,000 時間 = 約166日 = 約5ヶ月間であり、日単位での分割であれば 4,000日 = 約133ヶ月 = 約11年です。

上限に達すると Quota exceeded: Your table exceeded quota for Number of partition modifications to a column partitioned table とエラーが出てジョブが失敗します。
パーティションに 有効期限 を設けてデータが自動削除されるようにするなどの考慮を行いましょう。

また「1 つのジョブで変更されるパーティションの数」や「1 日の取り込み時間パーティション分割テーブルあたりのパーティションの変更回数」「1 日の列パーティション分割テーブルあたりのパーティション変更数」などにも上限があります。バッチ処理がこれに抵触していないかは、十分注意する必要があります。

参考: 割り当てと上限

参考情報

以下の公式ブログではパーティションやクラスタリングが詳細に解説されていますので、是非参考にしてください。

cloud.google.com

cloud.google.com

杉村 勇馬 (記事一覧)

クラウドソリューション部 部長

クラウド管理運用やネットワークに知見あり。AWSの全12資格をコンプリートしたので、次はGoogle Cloudの認定資格を狙っている。現在、Google Cloud認定資格は4冠。

2022年1月現在、ハマっているものはディズニープラスで見られるマーベルのドラマシリーズ。

(Wantedlyプロフィール)