カピバラ好きなエンジニアブログ

興味ある技術とか検証した内容を赴くままに書いていきます。カピバラの可愛さこそ至高。

AWS 規範ガイダンス「Using Apache Iceberg on AWS」Memo #2

はじめに

こんにちは、半田です。

前回に引き続き、AWS 規範ガイダンス「Using Apache Iceberg on AWS」を読んでメモをしていきたいと思います。
前回は「Introduction」と「Modern data lakes」のページを読みました。
今回は「Getting started with Apache Iceberg tables in Amazon Athena SQL」を読んでいきます。

docs.aws.amazon.com

前の記事の冒頭にも書きましたが、こういうメモをあまり書いたことがないので、「それまんま引用じゃね?」というところがあってもご容赦ください。
あと自分の理解を書いていますが、もしかしたら間違っている可能性もあるので、正しい情報は公式の各ドキュメントを参照するようにしてください。


目次


Getting started with Apache Iceberg tables in Amazon Athena SQLAmazon Athena SQLApache Icebergテーブルを使い始める)

このページではAmazon AthenaでApache Icebergを利用する際の使い方について紹介されています。
実際に利用する際にはAmazon Athenaドキュメントの以下の前提条件を実施している必要があります。

もしAmazon Athenaを利用したことがないという方であれば、Amazon Athenaドキュメントの以下のチュートリアルを実施した方がイメージしやすくなると思います。 docs.aws.amazon.com

本記事では上記作業については実施済みとして進めます。

Amazon Athenaでicebergテーブルを作成するときのSQLの基本構成は以下になります。
大部分は通常のAthena SQLと大きく変わりないですが、TBLPROPERTIESで「'table_type' ='ICEBERG'」と指定しているところが異なります。

CREATE TABLE <table_name> (
    col_1 string,
    col_2 string,
    col_3 bigint,
    col_ts timestamp)
PARTITIONED BY (col_1, <<<partition_transform>>>(col_ts))
LOCATION 's3://<bucket>/<folder>/<table_name>/'
TBLPROPERTIES ( 
    'table_type' ='ICEBERG'
)

詳細は以下のAthenaドキュメントを確認してください。 docs.aws.amazon.com

この後の手順で実際にicebergテーブルを作成していきますが、事前にAthenaにデータベースが必要なため、以下のコマンドを実行してデータベースだけ作成しておきます。
Athenaコンソールの左側のDatabaseが作成したデータベース名になっていることを確認しておきます。

CREATE DATABASE iceberg_db

また、実際にicebergテーブルのデータが書き込まれるS3バケットも作成しておきます。


パーティショニングされていないテーブルの作成

ここではパーティショニングされていないicebergテーブルを実際に作成します。
パーティショニングが何かについては次の項目で説明するため、ここでは省略します。

以下のSQLを実行してパーティショニングがないicebergテーブルを作成することができます。

CREATE TABLE athena_iceberg_table (
    color string,
    date string,
    name string,
    price bigint,
    product string,
    ts timestamp)
LOCATION 's3://DOC_EXAMPLE_BUCKET/ice_warehouse/iceberg_db/athena_iceberg_table/'
TBLPROPERTIES ( 
    'table_type' ='ICEBERG'
)

上記のSQLテンプレートをAthenaコンソールにコピペし、DOC_EXAMPLE_BUCKETの部分だけ先ほど作成したS3バケット名に修正して実行します。

CREATE TABLE athena_iceberg_table (
    color string,
    date string,
    name string,
    price bigint,
    product string,
    ts timestamp)
LOCATION 's3://test-tmp-20250602/ice_warehouse/iceberg_db/athena_iceberg_table/'
TBLPROPERTIES ( 
    'table_type' ='ICEBERG'
)

事前設定が問題なく実施されていれば、エラーなくテーブルが作成されると思います。

S3を確認すると、先ほどのSQLのLOCATIONで設定したS3バケットに指定したPrefixが作成され、その中にmetadataパスと*.metadata.jsonファイルが作成されていることが確認できます。

Jsonファイルには先ほどSQLで指定したicebergテーブルの情報等が記載されています。
このファイルがicebergにおいてどういう役割を果たしているのかは後の項目で説明されているので、ここではこういうファイルが作成されるんだな、ぐらいで理解しておきます。


パーティショニングされたテーブルの作成

次はパーティショニングされているicebergテーブルを実際に作成します。

Amazon Athenaではクエリでスキャンされたデータ量に対して課金が発生するため、都度該当テーブルのデータを全量スキャンしているとコストがかなり高くなってしまいます。
そのため、パーティション分割することでスキャン対象を制限することができ、スキャン時のコストを減らすことができます。
AthenaはApache Hive形式のパーティションを利用でき、*/year=2025/month=06/day=02/*というようにイコール記号を含むキーと値のペアをスキャン対象のS3パスに含むことでパーティションとして認識させることができます。
詳細は以下ドキュメントを見ていただきたいですが、手動作業は発生しますHive形式以外のパーティションにも対応しています。
docs.aws.amazon.com

ただ、今回のicebergテーブルにおいては上記のS3パス内に含めるパーティションの管理方式ではなく、icebergのhidden partitioningを利用します。
詳しくは以下に記載されていますが、S3パスにパーティションを含める形式だとどうしても物理的な構成に依存してしまいますが、icebergはパーティションを論理的な構成で管理することでデータの量の変化に合わせてパーティションを変更(進化と呼ばれている)できるようにしています。
iceberg.apache.org

以下のSQLを実行してパーティショニングがあるicebergテーブルを作成することができます。

CREATE TABLE athena_iceberg_table_partitioned (
    color string,
    date string,
    name string,
    price bigint,
    product string,
    ts timestamp)
PARTITIONED BY (day(ts))
LOCATION 's3://DOC_EXAMPLE_BUCKET/ice_warehouse/iceberg_db/athena_iceberg_table/'
TBLPROPERTIES ( 
    'table_type' ='ICEBERG'
)

上記のSQLではパーティション指定でday(ts)と記載していますが、これはタイムスタンプ型のtsカラムの値を元に、icebergのパーティション変換(day()変換)を利用してテーブルデータからパーテイション値への変換をしています。
パーテイション変換はday()変換以外にもあるので、詳しくは以下のicebergドキュメントをご参照ください。 iceberg.apache.org

上記のSQLテンプレートをAthenaコンソールにコピペし、DOC_EXAMPLE_BUCKETの部分だけ先ほど作成したS3バケット名に修正して実行します。
また、LOCATIONの最後のパスに_partitionedを付けて先ほど作成したテーブルのパスを被らないようにしておきます。

CREATE TABLE athena_iceberg_table_partitioned (
    color string,
    date string,
    name string,
    price bigint,
    product string,
    ts timestamp)
PARTITIONED BY (day(ts))
LOCATION 's3://test-tmp-20250602/ice_warehouse/iceberg_db/athena_iceberg_table_partitioned/'
TBLPROPERTIES ( 
    'table_type' ='ICEBERG'
)

こちらも事前設定が問題なく実施されていれば、エラーなくテーブルが作成されると思います。

S3を確認するとLOCATIONに指定したPrefixに同じようにファイルが出力されていることが確認できます。

*.metadata.jsonファイルを確認すると先ほどとほとんど同じような内容ですが、partition-specsのfields項目にSQLで指定した"ts_day"が指定されていることがわかります。
これでデータインポート時にtsカラムの値を元にパーティションが作成されます。


1つのCTASステートメントでテーブルを作成し、データをロードする

ここまではAthena SQLを利用して空のicebergテーブルを作成してきました。
次はAthena CTASを利用して、テーブル作成時にHive形式のテーブルのデータをicebergテーブルにロードします。

Athena CTASというのは別クエリの結果からAthenaに新しいテーブルを作成するクエリになります。
詳しくは以下に記載されているので、こちらをご確認ください。 docs.aws.amazon.com

Athena CTASを利用するには事前にHive形式のクエリ可能なテーブルを作成しておく必要があります。
今回は過去に作成したテーブルがあったので、こちらを利用します。

もしテーブルがなければ、以下のチュートリアルを実施して作成されたテーブルを利用するのが良いと思います。 docs.aws.amazon.com

以下のSQLを実行してパーティショニングがあるicebergテーブルを作成することができます。

CREATE TABLE iceberg_ctas_table WITH (
  table_type = 'ICEBERG',
  is_external = false,
  location = 's3://DOC_EXAMPLE_BUCKET/ice_warehouse/iceberg_db/iceberg_ctas_table/'
) AS
SELECT * FROM "iceberg_db"."hive_table" limit 20
---
SELECT * FROM "iceberg_db"."iceberg_ctas_table" limit 20

上記のSQLテンプレートをAthenaコンソールにコピペし、DOC_EXAMPLE_BUCKETの部分だけ先ほど作成したS3バケット名に修正します。
また、ASのすぐあとのSELECT文の対象DB・テーブルを先ほど確認した元テーブルを指定するように修正して実行します。
---以下のSELECT文はこの時点では削除しておきます。

CREATE TABLE iceberg_ctas_table WITH (
  table_type = 'ICEBERG',
  is_external = false,
  location = 's3://test-tmp-20250602/ice_warehouse/iceberg_db/iceberg_ctas_table/'
) AS
SELECT * FROM "test-db"."test_s3_raw_data" limit 20

エラーせず終了すると左のTables一覧にテーブル名が追加されます。

以下のSQLで作成したicebergテーブルにクエリすると元テーブルと同じデータが含まれていることが確認できます。

SELECT * FROM "iceberg_db"."iceberg_ctas_table" limit 20


S3上dataオブジェクト確認

locationで指定したS3 Prefixを確認すると、dataとmetadataの2つのFolder Typeオブジェクトが作成されています。

dataオブジェクトにはparquetファイルが作成されています。

このファイルをダウンロードしてDuckDB CLIを利用してparquetファイルの中身を見るとAthenaからクエリした内容と同じデータが含まれていることが分かります。

> duckdb -c "from '20250602_031345_00124_b2gpg-0d7dd949-81a2-4a98-9c61-a6f5ceca1e39.parquet'"
┌───────┬────────────┬───────┬──────────────┐
│  no   │    user    │  age  │   favorite   │
│ int64 │  varchar   │ int64 │   varchar    │
├───────┼────────────┼───────┼──────────────┤
│     1 │ test-user1 │    20 │ apple        │
│     2 │ test-user2 │    25 │              │
│     3 │ test-user3 │    21 │ banana       │
│     4 │ test-user4 │    19 │ peach,orange │
│     5 │ test-user5 │    29 │ apple        │
│     6 │ test-user6 │    20 │ grape,banana │
└───────┴────────────┴───────┴──────────────┘

DuckDB CLIPowerShellで以下コマンドを実行してインストールしました。

winget install DuckDB.cli

あとはこのあたりを参考にparquetファイルを直参照させて確認しました。 qiita.com


S3上metadataオブジェクト確認(*.metadata.json)

metadataオブジェクト配下には1つの*.metadata.jsonファイルと2つの*.avroファイルが作成されています。

まずは*.metadata.jsonファイルから見てみます。
先ほど空テーブルを作成したときのファイルと比べると内容が大きく変わっています。
全てを見るのは大変なので、特に変更がされた箇所に絞ってみていきます。

最初にpropertiesパラメータです。
以下が先ほど作成したパーティションありのicebergテーブルのmetadata.jsonファイルです。

  "properties" : {
    "write.object-storage.enabled" : "true",
    "write.object-storage.path" : "s3://test-tmp-20250602/ice_warehouse/iceberg_db/athena_iceberg_table_partitioned/data",
    "write.parquet.compression-codec" : "zstd"
  },

以下がCTASで作成したicebergテーブルで作成されたmetadata.jsonファイルになります。
"write.object-storage.enabled""write.parquet.compression-codec"は同じ値ですが、実際にデータが作成されているのでファイル形式とparquetの圧縮レベルが追加されていました。
その代わり"write.object-storage.path"は削除されているようです。

  "properties" : {
    "write.parquet.compression-level" : "3",
    "write.format.default" : "PARQUET",
    "write.object-storage.enabled" : "true",
    "write.parquet.compression-codec" : "ZSTD"
  },

propertiesの設定内容については以下に記載があります。 iceberg.apache.org

次にsnapshot周りの以下3つのパラメータです。

  "current-snapshot-id" : -1,
  "refs" : { },
  "snapshots" : [ ],

CTASで作成したicebergテーブルで管理されているsnapshot情報が記載されています。

  "current-snapshot-id" : 1306630801486021275,
  "refs" : {
    "main" : {
      "snapshot-id" : 1306630801486021275,
      "type" : "branch"
    }
  },
  "snapshots" : [ {
    "sequence-number" : 1,
    "snapshot-id" : 1306630801486021275,
    "timestamp-ms" : 1748834026764,
    "summary" : {
      "operation" : "append",
      "trino_query_id" : "20250602_031345_00124_b2gpg",
      "added-data-files" : "1",
      "added-records" : "6",
      "added-files-size" : "828",
      "changed-partition-count" : "1",
      "total-records" : "6",
      "total-files-size" : "828",
      "total-data-files" : "1",
      "total-delete-files" : "0",
      "total-position-deletes" : "0",
      "total-equality-deletes" : "0"
    },
    "manifest-list" : "s3://test-tmp-20250602/ice_warehouse/iceberg_db/iceberg_ctas_table/metadata/snap-1306630801486021275-1-736a9ac4-0737-4ce5-9d93-551292673836.avro",
    "schema-id" : 0
  } ],

以下はその名前の通り、現在の最新のスナップショットIDを指定しています。

"current-snapshot-id" : 1306630801486021275,

スナップショットはライフサイクル管理のために名前付き参照としてブランチとタグをサポートしているので、以下はブランチ形式で管理されているスナップショットの情報がここに記載されています。

  "refs" : {
    "main" : {
      "snapshot-id" : 1306630801486021275,
      "type" : "branch"
    }
  },

以下ドキュメントの記載を見る限りでは、ブランチやタグ毎にスナップショットの保持期間を指定することができるようです。 iceberg.apache.org

snapshotsではicebergが管理しているスナップショット情報の詳細が記載されています。

  "snapshots" : [ {
    "sequence-number" : 1,
    "snapshot-id" : 1306630801486021275,
    "timestamp-ms" : 1748834026764,
    "summary" : {
      "operation" : "append",
      "trino_query_id" : "20250602_031345_00124_b2gpg",
      "added-data-files" : "1",
      "added-records" : "6",
      "added-files-size" : "828",
      "changed-partition-count" : "1",
      "total-records" : "6",
      "total-files-size" : "828",
      "total-data-files" : "1",
      "total-delete-files" : "0",
      "total-position-deletes" : "0",
      "total-equality-deletes" : "0"
    },
    "manifest-list" : "s3://test-tmp-20250602/ice_warehouse/iceberg_db/iceberg_ctas_table/metadata/snap-1306630801486021275-1-736a9ac4-0737-4ce5-9d93-551292673836.avro",
    "schema-id" : 0
  } ],

各項目にどのような値が含まれるかは以下のドキュメントをご確認ください。 iceberg.apache.org

"summary"の項目については以下ドキュメントに記載されています。 大体はその名前からどういう値なのかを推測できそうです。 iceberg.apache.org

Icebergテーブルはデータの追加・更新・削除等が行われるとスナップショットが取得され、そのスナップショットからある特定の時間断面のデータを取得することができます。
ただ、データの変更が入るたびにスナップショットが作られるとそれを管理する必要があります。
Icebergテーブルに対してクエリされた際に、どのスナップショットからデータを読み出したら良いかを制御するためにマニフェストリストファイルが作成されます。
ここではそのマニフェストリストファイルのパスが指定されています。詳しくはこの後見ていきます。

    "manifest-list" : "s3://test-tmp-20250602/ice_warehouse/iceberg_db/iceberg_ctas_table/metadata/snap-1306630801486021275-1-736a9ac4-0737-4ce5-9d93-551292673836.avro",


S3上metadataオブジェクト確認(*.avro)

今度は先ほどマニフェストファイルとして出てきた*.avroファイルを見ていきます。
ここでは以下の2ファイルが作成されております。

  • snap-1306630801486021275-1-736a9ac4-0737-4ce5-9d93-551292673836.avro
  • 736a9ac4-0737-4ce5-9d93-551292673836-m0.avro

以下ドキュメントに記載されている通り、マニフェストファイルはicebergで管理されるファイルの一覧やパーティション、メトリクス等が含まれたAvro形式のファイルで、ファイルの追跡に利用されます。
今回だとファイル名先頭にsnap-*が付いていないファイルがマニフェストファイルになります。

マニフェストは、データファイルまたは削除ファイルの一覧と、各ファイルのパーティションデータタプル、メトリクス、および追跡情報を含む不変のAvroファイルです。 1つまたは複数のマニフェストファイルは、ある時点でのテーブル内のすべてのファイルを追跡するスナップショットを保存するために使用されます。 マニフェストは、各テーブルスナップショットのマニフェストリストによって追跡されます。 iceberg.apache.org

もう一つのsnap-*が付いているファイルはマニフェストリストファイルと呼ばれます。
マニフェストリストファイルにはicebergテーブルにクエリが行われたときに最小限のファイルのみスキャンするためのメタデータが含まれます。

スナップショットはテーブル メタデータに埋め込まれますが、スナップショットのマニフェストのリストは別のマニフェスト リスト ファイルに保存されます。 マニフェストリストには、テーブルスキャンを計画する際にスナップショット内のすべてのマニフェストをスキャンする手間を省くためのサマリーメタデータが含まれています。 これには、追加されたファイル、既存のファイル、削除されたファイルの数、およびマニフェストの書き込みに使用されたパーティション仕様の各フィールドの値のサマリーが含まれます。 iceberg.apache.org

実際のマニフェストファイルは以下のファイルです。

  • 736a9ac4-0737-4ce5-9d93-551292673836-m0.avro

Avro形式のファイルはバイナリでテキストエディターでは読めないため、以下の記事を参考にPythonで確認します。 qiita.com

最初のほうで少しエラーが出ていますが、実際のデータファイル(parquet)のパスやレコード数・サイズ等が細かく記録されています。

全ての項目は見切れないので、細かくは以下のドキュメントを参照ください。 iceberg.apache.org

次に実際のマニフェストリストファイルは以下のファイルになります。

  • snap-1306630801486021275-1-736a9ac4-0737-4ce5-9d93-551292673836.avro

同じようにPythonを利用して出力したファイル内容はこちらです。
マニフェストファイルへのパスやマニフェストファイルが追跡するファイルタイプ等が記載されています。

こちらもちょっと数が多いので、詳細は以下のドキュメントを参照ください。 iceberg.apache.org


データの挿入、更新、削除

ここまででAthena上でicebergテーブルを作成する方法を試してきました。
ここからは実際にテーブルへのデータの追加やクエリをしていきます。

AthenaからicebergテーブルへのデータのINSERT/UPDATE/DELETE操作等は通常のHive形式と同様のSQL操作で実施可能です。
規範ガイダンスでは例として次のようなSQLが記載されています。

INSERT INTO "iceberg_db"."ice_table" VALUES (
    'red', '222022-07-19T03:47:29', 'PersonNew', 178, 'Tuna', now()
)

SELECT * FROM "iceberg_db"."ice_table"
where color = 'red' limit 10;

ここでは以下ドキュメントの内容を参考に、作成したicebergテーブルのデータの操作を行っていきます。 docs.aws.amazon.com

操作前のicebergテーブルの状態は以下です。


INSERT INTO

以下のINSERTクエリを作成して実行します。

INSERT INTO iceberg_ctas_table VALUES (7,'test-user7',33,'apple,banana')

SELECT文を実行するとINSERTしたデータが含まれていることを確認できました。

SELECT * FROM "iceberg_db"."iceberg_ctas_table" limit 20

S3を確認すると新しくax5Nvgというオブジェクトが作成されていました。

追加されたparquetファイルをダウンロードしてduckdbで確認すると、追加したレコードのみ含まれていました。

> duckdb -c "from '20250602_081341_00012_4p8ka-e9e549df-eb49-48d4-b2a8-15c18e431b64.parquet'"
┌───────┬────────────┬───────┬──────────────┐
│  no   │    user    │  age  │   favorite   │
│ int64 │  varchar   │ int64 │   varchar    │
├───────┼────────────┼───────┼──────────────┤
│   7   │ test-user7 │  33   │ apple,banana │
└───────┴────────────┴───────┴──────────────┘

metadata側を確認すると先ほど確認したファイルの新しいファイルが作成されていました。

"*.metadata.json"ファイル(メタデータファイル)のどこが変更されたのか確認してみます。

左がデータ追加前、右がデータ追加後のファイルです。
まずは最終更新日とシーケンス番号が更新されてます。

シーケンス番号が追加されたことで最新のスナップショットIDも変更されてます。

新しいスナップショットのスナップショット情報も追加されています。

最後にスナップショットログとメタデータログが更新されていました。

次は"snap-*.avro"ファイル(マニフェストリストファイル)のどこが変更されたのか確認してみます。

先ほどと同じく左がデータ追加前、右がデータ追加後のファイルです。
こちらの差分は1箇所のみで、新しいスナップショットの情報が追加されているのみでした。

次は"*.avro"ファイル(マニフェストファイル)のどこが変更されたのか確認してみます。

こちらも差分は1箇所のみで、ここでは最初のスナップショットの情報は完全に残っておらず、data_fileの値が最新のスナップショットの情報に変わっていました。


DELETE

以下のDELETEクエリを作成して実行します。

DELETE FROM iceberg_ctas_table WHERE no=1

SELECT文を実行するとDELETEしたデータが削除されていることを確認できました。

SELECT * FROM "iceberg_db"."iceberg_ctas_table" limit 20

S3を確認すると新しく8HYtKAというオブジェクトが作成されていました。

追加されたparquetファイルをダウンロードしてduckdbで確認すると、削除したデータが含まれるparquetファイルのパスが記載されていました。

> duckdb -c "from '20250602_091306_00100_jbqpq-7b444c87-eb69-43f8-9856-387d2da66299.parquet'"
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬───────┐
│                                                                        file_path                                                                        │  pos  │
│                                                                         varchar                                                                         │ int64 │
├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼───────┤
│ s3://test-tmp-20250602/ice_warehouse/iceberg_db/iceberg_ctas_table/data/qzog6Q/20250602_031345_00124_b2gpg-0d7dd949-81a2-4a98-9c61-a6f5ceca1e39.parquet │   0   │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴───────┘

metadata側を確認すると新しいファイルが作成されていました。このあたりの動きは先ほどのINSERTと同じです。

メタデータファイルの中身の差分はほとんど先ほどと同じですが、スナップショットの"summary"項目の内容にファイル削除関連の項目が追加となっていました。

マニフェストファイルの差分は先ほどとほぼ変わらなかったので割愛します。


UPDATE

以下のDELETEクエリを作成して実行します。

UPDATE iceberg_ctas_table SET favorite='orange' WHERE no=2

SELECT文を実行するとUPDATEしたデータが更新されていることを確認できました。

SELECT * FROM "iceberg_db"."iceberg_ctas_table" limit 20

S3確認すると新しいparquetファイルが作成されており、中身を見るとUPDATEしたデータが含まれるparquetファイルのパスが設定されていました。

> duckdb -c "from '20250602_093420_00012_6awut-b3457432-9de5-4359-9cd9-441d22e7b347.parquet'"
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬───────┐
│                                                                        file_path                                                                        │  pos  │
│                                                                         varchar                                                                         │ int64 │
├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼───────┤
│ s3://test-tmp-20250602/ice_warehouse/iceberg_db/iceberg_ctas_table/data/qzog6Q/20250602_031345_00124_b2gpg-0d7dd949-81a2-4a98-9c61-a6f5ceca1e39.parquet │   1   │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴───────┘

メタデータファイル、マニフェストファイルの差分は先ほどとほぼ変わらなかったので割愛します。


Icebergテーブルへのクエリ

icebergテーブルには通常のSQLを実行できますが、SQLを利用してicebergテーブル情報等の特殊なデータをクエリすることもできます。
icebergテーブルは過去のスナップショットを保持しているため、ある特定の過去断面のデータをクエリすることができます。(=タイムトラベルクエリ)
以下のSQLを実行して、データを投入した直後のテーブルにあるデータを取得します。

SELECT * FROM iceberg_ctas_table FOR TIMESTAMP AS OF (current_timestamp - interval '300' minute)

no=7のデータが追加されておらず、no=1のデータが削除されておらず、no=2のデータが更新されていない、投入直後のデータが取得できました。

タイムトラベルクエリや他に実行可能なicebergテーブルデータのクエリについては以下のドキュメントに説明が載っています。 docs.aws.amazon.com docs.aws.amazon.com

Icebergテーブルの仕組み

icebergテーブルについて実際に操作してきましたが、ここではicebergテーブルの仕組みについて解説されています。
AWS規範ガイダンスからの引用ですが、AWS上で実装するicebergテーブルでは、以下のように3つのレイヤーで構成され、その実態としてはこれまで見てきたようにS3上のファイルで管理されています。 https://docs.aws.amazon.com/images/prescriptive-guidance/latest/apache-iceberg-on-aws/images/iceberg-table-anatomy.png

具体的な構成は以下になります。
IcebergをはじめとするOpen Table FormatはあくまでS3等のオブジェクトストレージ上に構成される仕組みであって、S3に組み込まれた機能などではないことは認識しておく必要があります。

  • Iceberg Catalog
    • AWS Glue Data CatalogはIcebergとネイティブに統合されており、ほとんどのユースケースにおいて、AWS上で実行されるワークロードのための最良の選択肢です。
    • Iceberg のテーブルとやり取りするサービス(例えば Athena)は、データの読み取りや書き込みのために、テーブルの現在のスナップショットバージョンを見つけるためにカタログを使用する。
  • Metadata Layer
    • Manifest files
      • 各データ・ファイルの場所、フォーマット、サイズ、チェックサム、およびその他の関連情報を含むレコードが含まれます。
    • Manifest lists
      • マニフェスト・ファイルのインデックスを提供します。
      • テーブル内のマニフェスト・ファイルの数が増えるにつれて、その情報をより小さなサブセクションに分割することで、クエリーによってスキャンされる必要のあるマニフェスト・ファイルの数を減らすことができます。
    • Metadata files
  • Data Layer
    • Data files
      • テーブルのデータレコードを含む。
    • Delete files
      • Icebergテーブルの行レベルの削除と更新操作をエンコードする。
      • Icebergのドキュメントで説明されているように、Icebergには2種類の削除ファイルがある。
      • これらのファイルは、merge-on-readモードを使用した操作によって作成される。


次回

今回はGetting started with Apache Iceberg tables in Amazon Athena SQLについて確認しました。
次回は以下ページを見ていきながら、可能なら実際に使い方も試してみたいと思います。

と、言いたいところなんですが、私がEMRのこととよくわかってないからかリソースの構築が中々うまくいかないので、EMRはスキップして次はAWS Glueのページを進めたいと思います。

おわりに

前回はてな機能試してみて使いづらかったのでMarkdownに戻しましたが、やはりこっちの方が直感的に書けるので書きやすいです。
長くなって見づらくなってしまいましたが、個人用のメモということでご容赦ください。