Apache Iceberg 用の BigQuery テーブルを操作してみる #cm_google_cloud_adcal_2024
はじめに
データ事業本部のkobayashiです。
クラスメソッドの Google Cloud Advent Calendar 2024 の 最終日のブログです。
BigQueryでApache Icebergテーブルのプレビューサポートが発表されました。この機能により、従来のBigQueryの管理機能やパフォーマンスを維持したまま、Apache IcebergフォーマットでデータをGoogle Cloud Storage(GCS)に保存できるようになります。
本記事では、この新機能の基本的な使い方と、実際に試してみて気づいた注意点などについて紹介していきます。
Apache Iceberg 用の BigQuery テーブルとは
Apache Iceberg用のBigQueryテーブル(以下、Icebergテーブル)は、オープンな形式でデータレイクハウスを構築するためのGoogle Cloudの機能になります。この機能により、従来のBigQueryテーブルの管理機能や使い勝手のままデータをGCSにParquet形式で保存できるようになりました。
主な特徴は、データを移動することなくBigQueryの分析機能を活用できることです。これにより、データガバナンスの観点からデータを自社管理下に置きたいというニーズと、BigQueryの強力な分析機能を活用したいというニーズを満たせます。
- Icebergテーブルのフルマネージド運用
- BigQueryの標準テーブルと同様のフルマネージド体験を提供
- 自動ストレージ最適化機能により、適応型ファイルサイズの設定や自動クラスタリング、ガベージコレクション、メタデータの最適化などが自動的に行われる
- 列レベルのセキュリティやデータマスキングなど、エンタープライズレベルのセキュリティ機能も利用可能
- 柔軟なデータ操作
- GoogleSQL DMLを使用したテーブル操作が可能で、データの更新や削除も自由に行える
- SparkやDataflowなどの外部エンジンとBigLakeコネクタを介して連携することで、バッチ処理や高スループットストリーミングの統合も可能
- スキーマ進化のサポート
- ビジネスニーズの変化に応じて、列の追加、削除、名前変更が可能で、既存の列のデータ型や列モードの変更も可能
一方以下のような制限もあるので使用する際はご注意ください。
- データの整合性を保つため、以下の点に特に注意が必要
- BigQuery外からのデータファイルの直接操作は厳禁
- 外部からファイルを追加した場合、そのデータは追跡されずに損失する可能性がある
- テーブル削除時、関連するデータファイルは自動的には削除されない
- 運用上の重要な考慮事項
- 新規テーブル作成は必ず空の接頭辞に対して行う
- テーブルごとに一意のURIを使用する
- バケットへの外部ツールからの書き込み権限は厳密に制限する
では実際にApache Iceberg 用の BigQuery テーブルを作成してみます。
Apache Iceberg 用の BigQuery テーブルを作成する
BigQueryにIceberg テーブルを作成するにはGCSに対して読み書きを行うのでGCSへのCloudリソース接続の設定を用意する必要があります。
はじめにこの設定を行いますが、以下のbqコマンドで作成します。
$ bq mk --connection --location=us --project_id={プロジェクトID} --connection_type=CLOUD_RESOURCE test-bq-iceberg
これでCloudリソース接続を作成できましたが接続設定に割り当てられたサービスアカウントにIcebergテーブルを作成するGCSへのオブジェクトの読み書き権限を付与する必要があります。これは外部接続を通してGCS上にIcebergのdataやmetadataを作成・読み取りするためです。
bq show --connection {プロジェクトID}.us.test-bq-iceberg
Connection {プロジェクトID}.us.test-bq-iceberg
name friendlyName description Last modified type hasCredential properties
--------------------------------- -------------- ------------- ----------------- ---------------- --------------- -----------------------------------------------------------------------------------------------
233151396088.us.test-bq-iceberg 16 Dec 16:42:26 CLOUD_RESOURCE False {"serviceAccountId": "bqcx-233151396088-7wjn@gcp-sa-bigquery-condel.iam.gserviceaccount.com"}
このコマンドで表示されたserviceAccountId
に対してroles/storage.objectViewer
とroles/storage.objectCreator
を付与します。
$ gcloud storage buckets add-iam-policy-binding gs://{バケット名} \
--member=serviceAccount:bqcx-233151396088-7wjn@gcp-sa-bigquery-condel.iam.gserviceaccount.com \
--role=roles/storage.objectCreator
$ gcloud storage buckets add-iam-policy-binding gs://{バケット名} \
--member=serviceAccount:bqcx-233151396088-7wjn@gcp-sa-bigquery-condel.iam.gserviceaccount.com \
--role=roles/storage.objectViewer
これで接続設定が終わったので次にIcebergテーブルを作成します。
今回作成するテーブルですが以下のようなparquetファイルをインポートするテーブルとなります。
FL_DATE | DEP_DELAY | ARR_DELAY | AIR_TIME | DISTANCE | DEP_TIME | ARR_TIME |
---|---|---|---|---|---|---|
2006-01-01 | 5 | 19 | 350 | 2475 | 9.083 | 12.483 |
2006-01-02 | 167 | 216 | 343 | 2475 | 11.783 | 15.767 |
2006-01-03 | -7 | -2 | 344 | 2475 | 8.883 | 12.133 |
2006-01-04 | -5 | -13 | 331 | 2475 | 8.917 | 11.95 |
2006-01-05 | -3 | -17 | 321 | 2475 | 8.95 | 11.883 |
2006-01-06 | -4 | -32 | 320 | 2475 | 8.933 | 11.633 |
2006-01-08 | -3 | -2 | 346 | 2475 | 8.95 | 12.133 |
それではBiqQueryで以下のSQLを実行します。
-- Icebergテーブル作成
CREATE TABLE `data_set_iceberg.flights` (
FL_DATE DATE, -- 日付型を使用
DEP_DELAY INT64,
ARR_DELAY INT64,
AIR_TIME INT64, -- 時間は整数として扱う
DISTANCE INT64, -- 距離は整数
DEP_TIME FLOAT64, -- 時刻は小数点形式
ARR_TIME FLOAT64
)
CLUSTER BY FL_DATE -- 日付でクラスタリング
WITH CONNECTION `projects/{プロジェクトID}/locations/us/connections/test-bq-iceberg`
OPTIONS (
file_format = 'PARQUET',
table_format = 'ICEBERG',
storage_uri = 'gs://{バケット名}/test-bg-iceberg/flights/'
);
これでIcebergテーブルが作成出来ました。テーブルの作成先に指定したGCSのオブジェクトを確認してみます。
gcloud storage ls -l -r --readable-sizes gs://{バケット名}/test-bg-iceberg/flights
gs://{バケット名}/test-bg-iceberg/flights/:
gs://{バケット名}/test-bg-iceberg/flights/metadata/:
107.00B 2024-12-16T07:50:49Z gs://{バケット名}/test-bg-iceberg/flights/metadata/v0.metadata.json
TOTAL: 1 objects, 107 bytes (107.00B)
するとmetadataディレクトリにmetadataのjsonファイルが作為されています。このv0.metadata.json
の中身を確認してみると以下のようなjsonでした。
{"properties":{"bigquery-table-id":"{プロジェクトID}.data_set_iceberg.flights"},"current-snapshot-id":-1}
Icebergテーブルにデータのデータを操作する
parquetのインポート
それではこのテーブルにparquetデータをインポートします。あたらじめインポートするparquetファイルをgs://{バケット名}/source/flights-1m.parquet
にアップロードしておきます。
> LOAD DATA INTO `data_set_iceberg.flights`
FROM FILES (
uris=['gs://{バケット名}/source/flights-1m.parquet'],
format='PARQUET');
これでIcebergテーブルにデータをインポートできたので確認してみます。
> SELECT * FROM `{プロジェクトID}.data_set_iceberg.flights` LIMIT 5;
| FL\_DATE | DEP\_DELAY | ARR\_DELAY | AIR\_TIME | DISTANCE | DEP\_TIME | ARR\_TIME |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| 2006-01-04 | -2 | -6 | 281 | 2475 | 15.216666221618652 | 23.299999237060547 |
| 2006-01-26 | -5 | -38 | 331 | 2586 | 15.75 | 18.71666717529297 |
| 2006-01-11 | 0 | 66 | 331 | 2454 | 8.25 | 17.600000381469727 |
| 2006-01-17 | 1 | -6 | 290 | 2454 | 8.266666412353516 | 16.399999618530273 |
| 2006-01-20 | -1 | 25 | 335 | 2446 | 18.733333587646484 | 22.33333396911621 |
テーブルにデータが登録されていることが確認できました。GCSも確認してみます。
$ gcloud storage ls -l -r --readable-sizes gs://{バケット名}/test-bg-iceberg/flights
gs://{バケット名}/test-bg-iceberg/flights/:
gs://{バケット名}/test-bg-iceberg/flights/data/:
7.98MiB 2024-12-16T08:00:38Z gs://{バケット名}/test-bg-iceberg/flights/data/11bc4f7e-8486-4500-a722-10760457bf30-726ea64391cfcf2d-f-00000-of-00001.parquet
gs://{バケット名}/test-bg-iceberg/flights/metadata/:
107.00B 2024-12-16T07:50:49Z gs://{バケット名}/test-bg-iceberg/flights/metadata/v0.metadata.json
TOTAL: 2 objects, 8362806 bytes (7.98MiB)
dataディレクトリにparquetファイルが作成されていることがわかります。
csvのインポート
次にcsvデータをインポートします。あたらじめインポートするparquetファイルをgs://{バケット名}/source/flights-1m.csv
にアップロードしておきます。
> LOAD DATA INTO `data_set_iceberg.flights`
FROM FILES (
uris=['gs://{バケット名}/source/flights-1m.csv'],
format='CSV',
skip_leading_rows = 1
);
これでIcebergテーブルにcsvファイルからデータをインポートできました。
テーブルにデータが登録されていることが確認できました。GCSのdataディレクトリを確認してみます。
$ gcloud storage ls -l -r --readable-sizes gs://{バケット名}/test-bg-iceberg/flights/data | sort -k2 -r
34.69kiB 2024-12-16T08:09:17Z gs://{バケット名}/test-bg-iceberg/flights/data/1fb05088-1ade-4368-aff2-df75a37f65ce-3c572a0966cfc0a7-f-00000-of-00001.parquet
7.98MiB 2024-12-16T08:00:38Z gs://{バケット名}/test-bg-iceberg/flights/data/11bc4f7e-8486-4500-a722-10760457bf30-726ea64391cfcf2d-f-00000-of-00001.parquet
新しくcsvを読み込んだのでdataディレクトリにその差分のparquetファイル1fb05088-1ade-4368-aff2-df75a37f65ce-3c572a0966cfc0a7-f-00000-of-00001.parquet
が作成されていることがわかります。
Insert文
次にInsert文を使ってデータをInsertしてみます。
> INSERT INTO `data_set_iceberg.flights` (FL_DATE,DEP_DELAY,ARR_DELAY,AIR_TIME,DISTANCE,DEP_TIME,ARR_TIME)
VALUES
('2024-12-01',5,19,350,2475,9.083,12.483),
('2024-12-02',167,216,343,2475,11.783,15.767),
('2024-12-03',-7,-2,344,2475,8.883,12.133),
('2024-12-04',-5,-13,331,2475,8.917,11.95),
('2024-12-05',-3,-17,321,2475,8.95,11.883),
('2024-12-06',-4,-32,320,2475,8.933,11.633),
('2024-12-08',-3,-2,346,2475,8.95,12.133);
Select文でデータがInsert出来たことを確認してみます。
> SELECT * FROM `{プロジェクトID}.data_set_iceberg.flights` WHERE FL_DATE >= '2024-12-01' LIMIT 10;
| FL\_DATE | DEP\_DELAY | ARR\_DELAY | AIR\_TIME | DISTANCE | DEP\_TIME | ARR\_TIME |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| 2024-12-02 | 167 | 216 | 343 | 2475 | 11.783 | 15.767 |
| 2024-12-01 | 5 | 19 | 350 | 2475 | 9.083 | 12.483 |
| 2024-12-05 | -3 | -17 | 321 | 2475 | 8.95 | 11.883 |
| 2024-12-06 | -4 | -32 | 320 | 2475 | 8.933 | 11.633 |
| 2024-12-03 | -7 | -2 | 344 | 2475 | 8.883 | 12.133 |
| 2024-12-04 | -5 | -13 | 331 | 2475 | 8.917 | 11.95 |
| 2024-12-08 | -3 | -2 | 346 | 2475 | 8.95 | 12.133 |
Insert文でデータを追加出来ていることがわかります。GCSのdataディレクトリを確認してみます。
$ gcloud storage ls -l -r --readable-sizes gs://{バケット名}/test-bg-iceberg/flights/data | sort -k2 -r
2.37kiB 2024-12-16T08:13:19Z gs://{バケット名}/test-bg-iceberg/flights/data/40a3e2fb-9c1b-4d7f-b82c-e5cc024b9281-e1ba57e0976265a5-f-00000-of-00001.parquet
34.69kiB 2024-12-16T08:09:17Z gs://{バケット名}/test-bg-iceberg/flights/data/1fb05088-1ade-4368-aff2-df75a37f65ce-3c572a0966cfc0a7-f-00000-of-00001.parquet
7.98MiB 2024-12-16T08:00:38Z gs://{バケット名}/test-bg-iceberg/flights/data/11bc4f7e-8486-4500-a722-10760457bf30-726ea64391cfcf2d-f-00000-of-00001.parquet
Insert文でデータを追加したのでdataディレクトリにその差分のparquetファイル40a3e2fb-9c1b-4d7f-b82c-e5cc024b9281-e1ba57e0976265a5-f-00000-of-00001.parquet
が作成されていることがわかります。
Update文
次はUpdate分でIcebergテーブルのデータを更新してみます。
> UPDATE `{プロジェクトID}.data_set_iceberg.flights` SET DEP_DELAY = 100 WHERE FL_DATE = '2024-12-01';
> SELECT * FROM `{プロジェクトID}.data_set_iceberg.flights` WHERE FL_DATE = '2024-12-01';
| FL\_DATE | DEP\_DELAY | ARR\_DELAY | AIR\_TIME | DISTANCE | DEP\_TIME | ARR\_TIME |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| 2024-12-01 | 100 | 19 | 350 | 2475 | 9.083 | 12.483 |
こちらも問題なくUpdateできました。GCS上のdataディレクトリを確認してみます。
> gcloud storage ls -l -r --readable-sizes gs://{バケット名}/test-bg-iceberg/flights/data | sort -k2 -r
2.65kiB 2024-12-16T08:46:18Z gs://{バケット名}/test-bg-iceberg/flights/data/ev_ZL6xSW79yB3XdVY2sNxyp5J2tZAb3O88QeoE1McA3To-f102f5a02d26cfcb-f-00000-of-00001.parquet
2.37kiB 2024-12-16T08:13:19Z gs://{バケット名}/test-bg-iceberg/flights/data/40a3e2fb-9c1b-4d7f-b82c-e5cc024b9281-e1ba57e0976265a5-f-00000-of-00001.parquet
34.69kiB 2024-12-16T08:09:17Z gs://{バケット名}/test-bg-iceberg/flights/data/1fb05088-1ade-4368-aff2-df75a37f65ce-3c572a0966cfc0a7-f-00000-of-00001.parquet
7.98MiB 2024-12-16T08:00:38Z gs://{バケット名}/test-bg-iceberg/flights/data/11bc4f7e-8486-4500-a722-10760457bf30-726ea64391cfcf2d-f-00000-of-00001.parquet
Update文で発生した差分が発生したのでdataディレクトリにparquetファイルev_ZL6xSW79yB3XdVY2sNxyp5J2tZAb3O88QeoE1McA3To-f102f5a02d26cfcb-f-00000-of-00001.parquet
が作成されています。
Delete文
最後にDelete分でIcebergテーブルのデータを削除してみます。
> DELETE FROM `data_set_iceberg.flights` WHERE FL_DATE <= '2006-02-01';
$ gcloud storage ls -l -r --readable-sizes gs://{バケット名}/test-bg-iceberg/flights/data | sort -k2 -r
1.36MiB 2024-12-16T08:49:25Z gs://{バケット名}/test-bg-iceberg/flights/data/ev_MwvpbFILghO24WBnVttrAv3lk8iyE1Tc9Gl148oJv78-cfeb88971c2b9717-f-00001-of-00002.parquet
1.97MiB 2024-12-16T08:49:25Z gs://{バケット名}/test-bg-iceberg/flights/data/ev_MwvpbFILghO24WBnVttrAv3lk8iyE1Tc9Gl148oJv78-cfeb88971c2b9717-f-00000-of-00002.parquet
2.65kiB 2024-12-16T08:46:18Z gs://{バケット名}/test-bg-iceberg/flights/data/ev_ZL6xSW79yB3XdVY2sNxyp5J2tZAb3O88QeoE1McA3To-f102f5a02d26cfcb-f-00000-of-00001.parquet
2.37kiB 2024-12-16T08:13:19Z gs://{バケット名}/test-bg-iceberg/flights/data/40a3e2fb-9c1b-4d7f-b82c-e5cc024b9281-e1ba57e0976265a5-f-00000-of-00001.parquet
34.69kiB 2024-12-16T08:09:17Z gs://{バケット名}/test-bg-iceberg/flights/data/1fb05088-1ade-4368-aff2-df75a37f65ce-3c572a0966cfc0a7-f-00000-of-00001.parquet
7.98MiB 2024-12-16T08:00:38Z gs://{バケット名}/test-bg-iceberg/flights/data/11bc4f7e-8486-4500-a722-10760457bf30-726ea64391cfcf2d-f-00000-of-00001.parquet
こちらもDelete文で発生した差分が発生したのでdataディレクトリにparquetファイルev_MwvpbFILghO24WBnVttrAv3lk8iyE1Tc9Gl148oJv78-cfeb88971c2b9717-f-00001-of-00002.parquet
が作成されています。
まとめ
Apache Iceberg用のBigQueryテーブル(Icebergテーブル)を試してみました。従来のBigQueryの管理機能やパフォーマンスを維持したまま、Apache IcebergフォーマットでデータをGoogle Cloud Storage(GCS)に保存できました。データガバナンスの要件を満たしながらBigQueryの分析機能を活用したい場合の有効な選択肢となります。今回は基本的な機能を検証しましたが、今後はより実践的なユースケースでの検証も進めていきたいです。
当アドベントカレンダー企画をお読み頂いた皆様、ありがとうございました!