
AWS Glue の Apache Iceberg ベースのマテリアライズドビューを試してみた #AWSreInvent
クラウド事業本部の石川です。re:Invent2025期間中にAWS Glue の Apache Icebergベースのマテリアライズドビュー(Materialized View) のサポートが発表されました。マテリアライズドビューを使うことで、事前に計算した集計結果を保存しておき、クエリのパフォーマンスを向上させることができます。
今回は、AWS Glue ETLジョブを使用して、Icebergベースのマテリアライズドビューを実際に作成・リフレッシュする手順を試してみました。下記のブログを参考に、実際に動作するのに設定やスクリプトなどを変更しています。
Apache Iceberg ベースのマテリアライズドビューとは
Apache Icebergベースのマテリアライズドビューは、以下の特徴を持っています。
- SQLベースでMVを定義・作成できる
- Amazon S3 Tables もしくは Iceberg形式でAmazon S3に保存される
- 自動リフレッシュスケジュールを設定可能
- フルリフレッシュとインクリメンタルリフレッシュをサポート
- Amazon Athena、Amazon EMR、AWS Glue の Apache Spark エンジンは、自動クエリ書き換えが可能
アーキテクチャ概要
ベーステーブルとなるordersテーブルを作成して、初期データを投入します。ordersテーブルからrders_summary_mvというマテリアライズドビューを作成します。ordersテーブルにデータを追加した後、rders_summary_mvをリフレッシュして差分更新します。
前提条件
マテリアライズドビューを作成するには、以下の条件が必要です。
- AWS Glue バージョン: 5.1 以上
- ソーステーブル形式: Apache Iceberg のみ(Hive、Hudi、Delta Lakeは非サポート)
- 命名規則: 3部名称(
glue_catalog.database.table)必須 - リージョン・アカウント: 同一リージョン、同一アカウント内
マテリアライズドビューを試す!
環境情報
| 項目 | 値 |
|---|---|
| リージョン | ap-northeast-1 |
| Glue バージョン | 5.1 |
| Worker Type | G.1X |
| Number of Workers | 2 |
Step 1: S3バケットの準備
Icebergテーブルのデータを保存するS3バケットを作成します。
# データレイク用バケット作成
aws s3 mb s3://cm-lakehouse-20251226 --region ap-northeast-1
# warehouseディレクトリ作成
aws s3api put-object \
--bucket cm-lakehouse-20251226 \
--key warehouse/
Step 2: IAMロールの準備
Glue ETLジョブ用のIAMロールには、以下のポリシーをアタッチします。
AWSGlueServiceRoleAmazonS3FullAccess(本番環境では最小権限に絞ることを推奨)- Lake Formation用カスタムポリシー(
lakeformation:GetDataAccess等)
# アタッチされたポリシーの確認
aws iam list-attached-role-policies \
--role-name AWSGlueServiceRoleDefault
Step 3: Lake Formationの設定
GlueロールをLake Formation Data Lake管理者に追加し、S3データロケーションを登録します。
# データロケーションの登録
aws lakeformation register-resource \
--resource-arn arn:aws:s3:::cm-lakehouse-20251226 \
--use-service-linked-role \
--region ap-northeast-1
# データロケーションへのアクセス権限付与
aws lakeformation grant-permissions \
--principal '{"DataLakePrincipalIdentifier":"arn:aws:iam::123456789012:role/AWSGlueServiceRoleDefault"}' \
--resource '{"DataLocation":{"ResourceArn":"arn:aws:s3:::cm-lakehouse-20251226"}}' \
--permissions "DATA_LOCATION_ACCESS" \
--region ap-northeast-1
Step 4: Glue ETLジョブの作成
ジョブパラメータ設定

色々と試行錯誤したのですが、Jobパラメーターに設定したのは以下の1つです。
| パラメータ | 値 |
|---|---|
--datalake-formats |
iceberg |

SparkSession
色々と試行錯誤したのですが、新しいSparkSessionを作成せず、GlueContextのspark_sessionを使用することです。それ以外は、Jobパラメーターで設定しています。
from awsglue.context import GlueContext
from pyspark.context import SparkContext
# 設定パラメータ
ACCOUNT_ID = "123456789012"
REGION = "ap-northeast-1"
WAREHOUSE_PATH = "s3://cm-lakehouse-20251226/warehouse"
# GlueContext初期化
sc = SparkContext()
glueContext = GlueContext(sc)
# GlueContextのspark_sessionを使用
spark = glueContext.spark_session
# Spark設定を追加
spark.conf.set("spark.sql.catalog.glue_catalog", "org.apache.iceberg.spark.SparkCatalog")
spark.conf.set("spark.sql.catalog.glue_catalog.warehouse", WAREHOUSE_PATH)
spark.conf.set("spark.sql.catalog.glue_catalog.glue.region", REGION)
spark.conf.set("spark.sql.catalog.glue_catalog.client.region", REGION)
spark.conf.set("spark.sql.catalog.glue_catalog.glue.id", ACCOUNT_ID)
spark.conf.set("spark.sql.catalog.glue_catalog.glue.account-id", ACCOUNT_ID)
spark.conf.set("spark.sql.catalog.glue_catalog.type", "glue")
spark.conf.set("spark.sql.catalog.glue_catalog.glue.lakeformation-enabled", "false")
spark.conf.set("spark.sql.defaultCatalog", "glue_catalog")
spark.conf.set("spark.sql.optimizer.answerQueriesWithMVs.enabled", "true")
Step 5: ベーステーブルの作成
まず、マテリアライズドビューの元となるIcebergテーブルを作成します。
# データベース作成
spark.sql("CREATE DATABASE IF NOT EXISTS glue_catalog.iceberg_mv")
spark.sql("USE glue_catalog.iceberg_mv")
# ベーステーブル作成
spark.sql("""
CREATE TABLE glue_catalog.iceberg_mv.orders (
order_id INT,
customer_name STRING,
product_category STRING,
amount DECIMAL(10, 2),
quantity INT,
order_date DATE,
region STRING
)
USING iceberg
PARTITIONED BY (region)
""")
# サンプルデータ挿入
spark.sql("""
INSERT INTO {FULL_TABLE_NAME} VALUES
(1, 'Tanaka Taro', 'Electronics', 150000.00, 1, DATE('2025-12-01'), 'Tokyo'),
(2, 'Suzuki Hanako', 'Clothing', 25000.00, 3, DATE('2025-12-02'), 'Osaka'),
(3, 'Yamamoto Ken', 'Electronics', 89000.00, 2, DATE('2025-12-03'), 'Tokyo'),
(4, 'Sato Yuki', 'Books', 5500.00, 5, DATE('2025-12-04'), 'Fukuoka'),
(5, 'Watanabe Akira', 'Electronics', 45000.00, 1, DATE('2025-12-05'), 'Tokyo'),
(6, 'Takahashi Mika', 'Clothing', 18000.00, 2, DATE('2025-12-06'), 'Osaka'),
(7, 'Ito Kenji', 'Books', 12000.00, 4, DATE('2025-12-07'), 'Sapporo'),
(8, 'Nakamura Emi', 'Electronics', 200000.00, 1, DATE('2025-12-08'), 'Tokyo'),
(9, 'Kobayashi Ryo', 'Clothing', 35000.00, 2, DATE('2025-12-09'), 'Osaka'),
(10, 'Kato Ayumi', 'Books', 8500.00, 3, DATE('2025-12-10'), 'Sapporo')
""")
作成したリソースとサンプルデータの確認
--- 現在のカタログ ---
+-----------------+
|current_catalog()|
+-----------------+
| glue_catalog|
+-----------------+
--- 現在のデータベース ---
+------------------+
|current_database()|
+------------------+
| iceberg_mv|
+------------------+
--- 作成されたテーブル一覧 ---
+----------+---------+-----------+
| namespace|tableName|isTemporary|
+----------+---------+-----------+
|iceberg_mv| orders| false|
+----------+---------+-----------+
--- テーブル詳細 ---
+--------------------+-------------+-------+
| col_name| data_type|comment|
+--------------------+-------------+-------+
| order_id| int| NULL|
| customer_name| string| NULL|
| product_category| string| NULL|
| amount|decimal(10,2)| NULL|
| quantity| int| NULL|
| order_date| date| NULL|
| region| string| NULL|
|# Partition Infor...| | |
| # col_name| data_type|comment|
| region| string| NULL|
+--------------------+-------------+-------+
サンプルデータを挿入しました。
--- ベーステーブルの内容 ---
+--------+--------------+----------------+---------+--------+----------+-------+
|order_id|customer_name |product_category|amount |quantity|order_date|region |
+--------+--------------+----------------+---------+--------+----------+-------+
|1 |Tanaka Taro |Electronics |150000.00|1 |2025-12-01|Tokyo |
|2 |Suzuki Hanako |Clothing |25000.00 |3 |2025-12-02|Osaka |
|3 |Yamamoto Ken |Electronics |89000.00 |2 |2025-12-03|Tokyo |
|4 |Sato Yuki |Books |5500.00 |5 |2025-12-04|Fukuoka|
|5 |Watanabe Akira|Electronics |45000.00 |1 |2025-12-05|Tokyo |
|6 |Takahashi Mika|Clothing |18000.00 |2 |2025-12-06|Osaka |
|7 |Ito Kenji |Books |12000.00 |4 |2025-12-07|Sapporo|
|8 |Nakamura Emi |Electronics |200000.00|1 |2025-12-08|Tokyo |
|9 |Kobayashi Ryo |Clothing |35000.00 |2 |2025-12-09|Osaka |
|10 |Kato Ayumi |Books |8500.00 |3 |2025-12-10|Sapporo|
+--------+--------------+----------------+---------+--------+----------+-------+
Step 6: マテリアライズドビューの作成
地域別・カテゴリ別の売上サマリーを集計するマテリアライズドビューを作成します。
spark.sql("""
CREATE MATERIALIZED VIEW glue_catalog.iceberg_mv.orders_summary_mv
AS SELECT
region,
product_category,
COUNT(*) AS order_count,
SUM(quantity) AS total_quantity,
SUM(amount) AS total_amount,
AVG(amount) AS avg_amount,
MIN(order_date) AS first_order_date,
MAX(order_date) AS last_order_date
FROM glue_catalog.iceberg_mv.orders
GROUP BY region, product_category
""")
Step 7: マテリアライズドビューの確認
作成したマテリアライズドビューの内容を確認します。
spark.sql("""
SELECT * FROM glue_catalog.iceberg_mv.orders_summary_mv
ORDER BY region, product_category
""").show()
実行結果
+-------+----------------+-----------+--------------+------------+-------------+----------------+---------------+
|region |product_category|order_count|total_quantity|total_amount|avg_amount |first_order_date|last_order_date|
+-------+----------------+-----------+--------------+------------+-------------+----------------+---------------+
|Fukuoka|Books |1 |5 |5500.00 |5500.000000 |2025-12-04 |2025-12-04 |
|Osaka |Clothing |3 |7 |78000.00 |26000.000000 |2025-12-02 |2025-12-09 |
|Sapporo|Books |2 |7 |20500.00 |10250.000000 |2025-12-07 |2025-12-10 |
|Tokyo |Electronics |4 |5 |484000.00 |121000.000000|2025-12-01 |2025-12-08 |
+-------+----------------+-----------+--------------+------------+-------------+----------------+---------------+
Step 8: マテリアライズドビューのリフレッシュ
ベーステーブルにデータを追加した後、マテリアライズドビューをリフレッシュします。
# 新しいデータを挿入
spark.sql("""
INSERT INTO glue_catalog.iceberg_mv.orders VALUES
(11, 'Morita Sota', 'Electronics', 320000.00, 1, DATE('2025-12-11'), 'Tokyo'),
(12, 'Honda Sakura', 'Books', 15000.00, 6, DATE('2025-12-12'), 'Nagoya')
""")
# フルリフレッシュ実行
spark.sql("REFRESH MATERIALIZED VIEW glue_catalog.iceberg_mv.orders_summary_mv FULL")
リフレッシュ後の結果
+-------+----------------+-----------+--------------+------------+-------------+----------------+---------------+
|region |product_category|order_count|total_quantity|total_amount|avg_amount |first_order_date|last_order_date|
+-------+----------------+-----------+--------------+------------+-------------+----------------+---------------+
|Fukuoka|Books |1 |5 |5500.00 |5500.000000 |2025-12-04 |2025-12-04 |
|Nagoya |Books |1 |6 |15000.00 |15000.000000 |2025-12-12 |2025-12-12 |
|Osaka |Clothing |3 |7 |78000.00 |26000.000000 |2025-12-02 |2025-12-09 |
|Sapporo|Books |2 |7 |20500.00 |10250.000000 |2025-12-07 |2025-12-10 |
|Tokyo |Electronics |5 |6 |804000.00 |160800.000000|2025-12-01 |2025-12-11 |
+-------+----------------+-----------+--------------+------------+-------------+----------------+---------------+
Nagoya地域のデータが追加され、Tokyo地域のElectronicsの集計値が更新されていることが確認できます。
Step 9: テーブルプロパティの確認
マテリアライズドビューのプロパティを確認すると、MVであることを示す情報が確認できます。
spark.sql("SHOW TBLPROPERTIES glue_catalog.iceberg_mv.orders_summary_mv").show(truncate=False)
主なプロパティ
| プロパティ | 説明 |
|---|---|
isMaterializedView |
マテリアライズドビューかどうか(true) |
lastRefreshType |
最後のリフレッシュタイプ(FULL/INCREMENTAL) |
viewOriginalText |
MVを定義したSQLクエリ |
current-snapshot-id |
現在のIcebergスナップショットID |
ハマったポイント
1. Lake Formation権限エラー
lakeformation-enabled=true を設定した場合、以下のエラーが発生しました。
Error Category: PERMISSION_ERROR
Failed to retrieve AWS Lake Formation temporary credentials for table
arn:aws:glue:ap-northeast-1:123456789012:table/iceberg_mv/orders with permission ALL.
Error: AccessDeniedException
原因: 新規作成されたテーブルに対するLake Formationのテンポラリクレデンシャル取得に失敗
解決策: lakeformation-enabled=false に設定
2. SparkSessionの問題
元のスクリプトでは SparkSession.builder.getOrCreate() で新しいSparkSessionを作成していたため、GlueContextの認証コンテキストが継承されませんでした。
修正前(NG):
from pyspark.sql import SparkSession
spark = SparkSession.builder \
.config("spark.sql.catalog.glue_catalog", "...") \
.getOrCreate() # 新しいセッションを作成 -> 認証コンテキストが継承されない
修正後(OK):
spark = glueContext.spark_session # GlueContextのセッションを使用
spark.conf.set("spark.sql.catalog.glue_catalog", "...") # 既存セッションに設定を追加
3. client.region設定の不足
spark.sql.catalog.glue_catalog.client.region を設定しないと、以下のエラーが発生します。
region must not be null
そのため、設定を追加しました。
spark.conf.set("spark.sql.catalog.glue_catalog.client.region", "ap-northeast-1")
実行時間
今回の検証では、G.1X × 2ワーカー × 1分14秒程度でした。

最後に
AWS Glue Data CatalogのApache Icebergベースのマテリアライズドビューを実際に実行するには、SparkSessionやJobパラメータの設定で試行錯誤しました。特にマテリアライズドビューを作成する権限不足に悩まされました。
その他には以下の設定をしました。
- GlueContextのspark_sessionを使用し、新規SparkSessionを作成しないことが重要
client.regionの設定を忘れないこと- Lake Formation連携は、現時点では
lakeformation-enabled=falseで動作
Apache Iceberg ベースのマテリアライズドビューの複雑な集計クエリを事前計算しておくことで、分析クエリのパフォーマンスを向上させることができます。更にAmazon Athena、Amazon EMR、AWS Glue の Apache Spark エンジンは自動クエリ書き換えに対応しており、ベーステーブルのクエリに対しても暗黙的にマテリアライズドビューのパフォーマンス改善を享受できます。
一方、本家Apache IcebergのMaterialized View仕様の検討中であり、こちらの機能も気になるところです。
合わせて読みたい








