S3 Tables の新機能 「Variant データ型」を EMR Serverless で試してみた

S3 Tables の新機能 「Variant データ型」を EMR Serverless で試してみた

S3 Tables で Iceberg V3 の Variant データ型がサポートされました。固定スキーマを定義しなくても JSON をそのままカラムに格納でき、JSONPath 形式のパスで型を指定して取り出せます。EMR Serverless の Spark 4.0 から、テーブル作成・書き込み・抽出・フィルタまで一通り動かして確認しました。
2026.07.29

はじめに

2026年7月28日、S3 Tables が Apache Iceberg V3 の Variant データ型に対応しました。Variant 型はネストした JSON をひとつのカラムにそのまま格納できる型で、事前にスキーマを定義しなくても半構造化データを扱えます。読み取り時は JSONPath 形式のパスで必要なフィールドだけを型付きで取り出せます。What's New ではシュレッドによるファイルプルーニング効果も挙げられていますが、本記事では検証していません。

https://aws.amazon.com/about-aws/whats-new/2026/07/amazon-s3-tables-variant-iceberg-v3/

検証内容

検証環境

Variant 型の DDL と DML には Spark 4.0 系が必要です。VARIANT データ型は Apache Spark 4.0.0 で追加されました。今回は Spark 4.0.2 と Iceberg 1.10.1 を含む EMR Serverless emr-spark-8.0.0 を東京リージョンで使いました。

S3 Tables への接続は Glue Iceberg REST endpoint 経由です。Spark 側のカタログ設定は次のとおりです。

spark.sql.catalog.s3t = org.apache.iceberg.spark.SparkCatalog
spark.sql.catalog.s3t.type = rest
spark.sql.catalog.s3t.uri = https://glue.ap-northeast-1.amazonaws.com/iceberg
spark.sql.catalog.s3t.warehouse = 123456789012:s3tablescatalog/variant-test-bucket
spark.sql.catalog.s3t.rest.sigv4-enabled = true
spark.sql.catalog.s3t.rest.signing-name = glue
spark.sql.catalog.s3t.rest.signing-region = ap-northeast-1

warehouse は ARN ではなく、アカウント ID に s3tablescatalog/<バケット名> を続けた形式で指定します。

テーブル作成

payload を VARIANT 型として宣言したテーブルを作成しました。Variant 型は V3 で追加された型なので、format-version に 3 を指定する必要があります。V3 テーブルの作成方法は Apache Iceberg V3 の利用 にまとまっています。

CREATE TABLE IF NOT EXISTS s3t.myns.variant_demo (
    event_id STRING NOT NULL,
    device_id STRING NOT NULL,
    event_ts TIMESTAMP NOT NULL,
    payload VARIANT NOT NULL
)
USING iceberg
TBLPROPERTIES ('format-version' = '3')

データ書き込み

JSON 文字列を parse_json() に渡すと VARIANT に変換されます。センサーデータを模した3件を INSERT しました。3件目は tags キーを持たないデータにしました。

INSERT INTO s3t.myns.variant_demo VALUES
  ('evt_001', 'sensor_A', current_timestamp(),
   parse_json('{"temperature": 23.5, "humidity": 62.1, "location": {"city": "Tokyo", "building": "HQ"}, "tags": ["indoor", "floor3"]}')),
  ('evt_002', 'sensor_B', current_timestamp(),
   parse_json('{"temperature": 31.2, "humidity": 78.5, "location": {"city": "Osaka", "building": "Lab"}, "tags": ["outdoor"]}')),
  ('evt_003', 'sensor_C', current_timestamp(),
   parse_json('{"temperature": 18.9, "humidity": 45.0, "location": {"city": "Nagoya", "building": "DC"}}'))

書き込み後にテーブルを読み込み、DataFrame のスキーマを確認しました。

root
 |-- event_id: string (nullable = false)
 |-- device_id: string (nullable = false)
 |-- event_ts: timestamp (nullable = false)
 |-- payload: variant (nullable = false)

payload が variant として認識されています。

データ読み取りとフィルタ

variant_get() には、対象カラム・JSONPath 形式のパス・取り出す型の3つを渡します。

SELECT
  event_id,
  device_id,
  variant_get(payload, '$.temperature', 'DOUBLE') AS temperature,
  variant_get(payload, '$.humidity', 'DOUBLE') AS humidity,
  variant_get(payload, '$.location.city', 'STRING') AS city,
  variant_get(payload, '$.tags[0]', 'STRING') AS first_tag
FROM s3t.myns.variant_demo
+--------+---------+-----------+--------+------+---------+
|event_id|device_id|temperature|humidity|city  |first_tag|
+--------+---------+-----------+--------+------+---------+
|evt_001 |sensor_A |23.5       |62.1    |Tokyo |indoor   |
|evt_002 |sensor_B |31.2       |78.5    |Osaka |outdoor  |
|evt_003 |sensor_C |18.9       |45.0    |Nagoya|NULL     |
|evt_001 |sensor_A |23.5       |62.1    |Tokyo |indoor   |
|evt_002 |sensor_B |31.2       |78.5    |Osaka |outdoor  |
|evt_003 |sensor_C |18.9       |45.0    |Nagoya|NULL     |
+--------+---------+-----------+--------+------+---------+

tags を持たない evt_003 の first_tag は NULL になり、キーが存在しない行でもクエリ全体は失敗しません。ネストしたフィールドや配列要素も、指定したパスのとおり取得できています。表示結果に同じ値の行が2組あるのは、CREATE TABLE IF NOT EXISTS でテーブルを作り直さずに同じ INSERT INTO を再実行したためです。

抽出した値は WHERE 句でもそのまま使えます。気温が 25.0 を超える行を絞り込みました。

SELECT
  event_id,
  device_id,
  variant_get(payload, '$.temperature', 'DOUBLE') AS temperature,
  variant_get(payload, '$.location.city', 'STRING') AS city
FROM s3t.myns.variant_demo
WHERE variant_get(payload, '$.temperature', 'DOUBLE') > 25.0
+--------+---------+-----------+-----+
|event_id|device_id|temperature|city |
+--------+---------+-----------+-----+
|evt_002 |sensor_B |31.2       |Osaka|
|evt_002 |sensor_B |31.2       |Osaka|
+--------+---------+-----------+-----+

JSON 文字列をパースする前処理を挟まずに、SQL の中で数値フィルタまで完結できます。

まとめ

JSON のキー構成が固まっていない段階でも、テーブル定義を確定させないまま S3 Tables へ取り込み始められます。どのフィールドを使うかは書き込んだ後から SQL 側で決められるので、IoT のセンサーデータやアプリケーションのイベントログのように送信元ごとにキーが違うデータの受け皿として使えます。

ただし AWS Glue のバージョン は最新の 5.1 でも Spark 3.5.6 であり、Variant 型を使うには EMR のように Spark 4.0 系を選べる実行環境が前提になります。また、キーが欠けた行の抽出結果は NULL になるため、どのキーが必ず存在するかの取り決めは書き込み側か読み取り側のどこかで必要になります。実行環境の制約と、キーの管理をどこで担保するかを評価したうえで採用を判断することをおすすめします。

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事