BigQuery 上で Apache Iceberg 用の BigQuery テーブル操作と Google Colab (spark)でのデータ参照の実施方法

## 概要

Apache Iceberg 用の BigQuery テーブルについて、基本的な動作検証を行った結果を共有します。本記事では、BigQuery 上で Apache Iceberg 用テーブルを操作する方法と、Google Colab（Spark）でデータを参照する方法を紹介します。

BigQuery では、以下の 3 種類の Apache Iceberg テーブルを作成できます。本記事の対象は Apache Iceberg 用の BigQuery テーブルです。

1. Apache Iceberg 用の BigLake 外部テーブル（BigLake Iceberg テーブル）
1. BigQuery Metastore Iceberg テーブル
1. Apache Iceberg 用の BigQuery テーブル（Iceberg マネージド テーブル / BigQuery Iceberg テーブル）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/50de7bcf-ba48-4c7f-ae86-de8fda7381e7.png)

出所：[Apache Iceberg 用の BigQuery テーブル  | Google Cloud](https://cloud.google.com/bigquery/docs/iceberg-tables?hl=ja#sql)

Apache Iceberg 用の BigQuery テーブルでは、metadata layer のオブジェクトが作成されません。そのため、外部サービスから接続する場合は `EXPORT TABLE METADATA` SQL ステートメントを実行して、Apache Iceberg のメタデータファイルを出力する必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/70177270-dd14-4576-af4c-14983ce0676f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/75975d67-c16e-4fc2-8b5d-49359433b7c1.png)

Apache Iceberg 用の BigLake 外部テーブルの操作手順については、以下の記事で紹介しています。

- [Google Colab で操作した Apache Iceberg のデータから Apache Iceberg 用の BigLake 外部テーブルを作成する方法 #BigQuery - Qiita](https://qiita.com/manabian/items/1f252dc06ef7101ffbf2)

また、Google Colab 上の Spark で Apache Iceberg テーブルを扱う方法は、以下の記事で解説しています。本記事では、あらかじめこの記事の設定が実施済みであることを前提としています。

- [Google Colab による Google Cloud Storage上でのApache Icebergデータ操作方法 #Spark - Qiita](https://qiita.com/manabian/items/cb1b5eeb39595898bc78)

## 手順

### GCS のバケットを作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/e598640c-c108-42be-9966-886b42cf9216.png)

### BigQuery にて GCS に対する接続を作成

Google Cloud コンソールの BigQuery 画面で `+ Add` をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/e729bd8c-7b4c-4dc3-923e-c8126464c612.png)

`Connections to external data sources` を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/5968c5ee-1454-4101-af17-91af127dcb76.png)

`Connection type` に `Vertex AI remote models, remote functions and BigLake (Cloud Resource)` を指定し、`Connection ID` に任意の名称を設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/6708400d-7ff5-40ba-8a14-fe42c12532cd.png)

作成した接続の `Service account id` を確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/54e44d0f-1c04-403c-87ec-47af50e95594.png)

続いて、GCS バケットの IAM 設定で、上記 `Service account id` に必要な権限（`Storage Admin`）を付与します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/6de6f6d2-daeb-4533-8f77-8c2b8eca3aeb.png)

### BigQuery 上で Apache Iceberg 用の BigQuery テーブルを作成

次に、BigQuery で下記の SQL を実行し、Apache Iceberg 用の BigQuery テーブルを作成します。`{connection_id}` には先ほど作成した接続の ID を指定してください。

```sql
CREATE OR REPLACE TABLE qiita.fist_table (
  id     INT,
  name   STRING,
  status STRING
)
WITH CONNECTION `{connection_id}`
OPTIONS (
  file_format = 'PARQUET',
  table_format = 'ICEBERG',
  storage_uri = 'gs://bg_iceberg/qiita/fist_table'
);
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/e50fdfa6-1c8e-4844-9d7e-c50a357229db.png)

BigQuery Studio 上で接続を表示したときの詳細画面にて、Connection ID を確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/05b63a39-e7f8-4cc5-9f0f-7265e9389496.png)

テーブル作成後、GCS のバケットを確認すると `v0.metadata.json` というファイルが生成されています。ただし、これは通常の Apache Iceberg メタデータファイルとは異なるようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/1b9fc367-8d5f-4f65-a604-40169e7c531a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/7e249fa5-c241-4ce5-858e-782e0758512f.png)

### データの挿入確認

次に、下記の SQL でテーブルにデータを挿入します。

```sql
INSERT INTO
    qiita.fist_table (id, name, status)
VALUES
    (1, 'Alice', 'active'),
    (2, 'Bob', 'active'),
    (3, 'Charlie', 'active'),
    (4, 'Daisy', 'active'),
    (5, 'Ethan', 'active');
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/72d4883d-fa0e-494f-9ac6-5220276bbbc2.png)

クエリを実行すると、正常にデータを取得できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/c4c8a5e7-28f2-4d07-957c-9ffea7765297.png)

GCS のバケットを再度確認すると、`data` ディレクトリが作成され、その中に Parquet ファイルが生成されていることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/07cd0da3-e323-483e-9056-7223c09a85a1.png)

ただし、`metadata` ディレクトリにある `v0.metadata.json` の内容は変化していません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/a8f52dbf-003e-4747-998e-acb5b9f6b7f4.png)

### Google Colab から Apache Iceberg 用の BigQuery テーブルを参照

#### Google

まず、Spark のバージョンを確認します。

```python
!pyspark --version
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/ab2c2913-c552-4a03-998a-5da27e539da2.png)

Google Drive をマウントします。

```python
from google.colab import drive
drive.mount('/content/drive')
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/976eebe9-72b4-41ac-a9c5-88ee1b8d1961.png)

続いて、SparkSession を定義します。

```python
gcs_iceberg_path = "gs://bg_iceberg/"
gcs_key_path = "/content/drive/MyDrive/Colab Notebooks/bigquery_iceberg/gcs_key.json"

from pyspark.sql import SparkSession

spark = (
    SparkSession.builder
        .appName("gcs_test")
        .config("spark.jars", "https://storage.googleapis.com/hadoop-lib/gcs/gcs-connector-hadoop3-latest.jar")
        .config('spark.jars.packages',
            'org.apache.iceberg:iceberg-spark-runtime-3.5_2.12:1.8.1,'
            'org.apache.iceberg:iceberg-gcp-bundle:1.8.1')
        .config('spark.sql.extensions', 'org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions')
        .config('spark.sql.catalog.spark_catalog', 'org.apache.iceberg.spark.SparkCatalog')
        .config("spark.sql.catalog.spark_catalog.type", "hadoop")
        .config("spark.sql.catalog.spark_catalog.warehouse", gcs_iceberg_path)
        .config("spark.hadoop.fs.gs.impl", "com.google.cloud.hadoop.fs.gcs.GoogleHadoopFileSystem")
        .config("google.cloud.auth.service.account.enable", "true")
        .config("spark.hadoop.google.cloud.auth.service.account.enable", "true")
        .config("spark.hadoop.google.cloud.auth.service.account.json.keyfile", gcs_key_path)
        .getOrCreate()
)
spark
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/bed62b86-2958-4d01-b116-02964f783934.png)

上記の設定が完了したうえでテーブルを参照すると、以下のエラーが発生します。これは、Apache Iceberg が必要とするメタデータファイル（metadata layer）が存在していないためです。

```python
spark.table("qiita.fist_table").show()
```

> IllegalArgumentException: Cannot parse missing int: format-version

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/b1afe3b3-4b87-4c21-a1ad-4aebae753dda.png)

BigQuery Studio に戻り、以下の SQL でメタデータファイルを出力します。

```sql
EXPORT TABLE METADATA FROM qiita.fist_table;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/40f6ba32-9e23-4fd9-9f1e-9dceae1319f7.png)

実行後、`v1742113850.metadata.json` と `version-hint.text` が生成されます。`v1742113850.metadata.json` には Apache Iceberg のメタデータが、`version-hint.text` には最新のメタデータファイル バージョンが記載されています。ここで表示される 1742113850 は、おそらく Unix Timestamp なので、実行時期によって値は異なります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/1d6ef88f-8340-43ac-90da-25d3b4548f47.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/94c3a52a-a9cc-4a4e-b70f-e08535c00777.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/1190db21-54f8-44e0-aaef-9161cd0c9ec4.png)

再度 Google Colab でテーブルのデータを取得すると、メタデータが生成されたため問題なく参照できます。

```python
spark.table("qiita.fist_table").show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/53260d45-5c9a-4432-80b1-a3dd73b226c4.png)

#### Apache Iceberg 用の BigQuery テーブルに対する DELETE 実行と Google Colab での参照

BigQuery Studio で、以下の DELETE 文を実行します。

```sql
DELETE FROM 
    qiita.fist_table
    WHERE
        id > 3;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/2945fdfe-16bf-40e8-9a7e-e7afc0af04a1.png)

GCS のバケットを確認しても、ファイルに変化はありません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/b858c384-deda-485c-9b2e-e55047424600.png)

Google Colab からテーブルを参照してみると、まだ DELETE は反映されていません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/1f1529fa-1360-4b2f-9c39-d74caaec523c.png)

そこで、再度 BigQuery Studio に戻り、下記の SQL を実行して最新のメタデータを出力します。

```sql
EXPORT TABLE METADATA FROM qiita.fist_table;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/b7bf9019-f719-4290-897d-2f8d9e31c0e7.png)

`v1742114161.metadata.json` という新しいファイルが生成されたことを確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/c383c9dd-c1b4-42b7-834c-62039e54eb7a.png)

この後、Google Colab から再びテーブルを参照すると、DELETE が反映された状態のデータを確認できます。

```python
spark.table("qiita.fist_table").show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/b16f6366-01f3-43d2-9eab-6b2d84cda039.png)