S3 Tablesを使ってIcebergに触れてみる

本記事は「 [TTDC Advent Calendar 2024](https://qiita.com/advent-calendar/2024/ttdc) 」 12 日目の記事です。

AWS re:Invent 2024でAmazon S3 Tablesが発表され、Apache Icebergをサポートする初のクラウドオブジェクトストアが誕生しました。AWSはこれまでにもAthenaやRedshiftなどのサービスで従来のDatalakeの弱点を解消してきました。その中核技術としてIcebergをはじめとするOpenTableFormatに対応してきましたが、今回はS3の機能として追加された形になります。[AWSニュースブログ](https://aws.amazon.com/jp/blogs/aws/new-amazon-s3-tables-storage-optimized-for-analytics-workloads/)によると「セルフマネージドテーブルストレージと比較すると、最大 3 倍のクエリパフォーマンスと最大 10 倍のトランザクション/秒が期待できます。」とのこと

今回は、最近よく聞くIceberg（OpenTableFormat）とはなにか？をGAとなったS3 Tablesを使いながらざっくりと理解していきます。

従来のDatalakeフォーマットに関してとっても参考になる神スライド
「AWSにおけるHudi/Iceberg/Delta Lakeの使いどころと違いについて」
https://pages.awscloud.com/rs/112-TZM-766/images/AWS-Black-Belt_2023_Datalake-Format-On-AWS_0516_v1.pdf

# OpenTableFormatとは
OpenTableFormatとしてIceberg, Hudi, DeltaLakeが上げられます。これらのフォーマットでは従来のDatalakeでの課題がいくらか解消されています。例えば従来のDatalakeで課題となっていた「スキーマ変更への追従が大変」という問題を解決するためIcebergではテーブルへの変更をスナップショットログで追跡する機能が搭載されています。

OpenTableFormatが分かりすぎる神サイト
「データレイクの新しいカタチ：Open Table Formatの紹介」
https://bering.hatenadiary.com/entry/2023/07/17/235246

# Icebergとは（OpenTableFormatの代表格）
catalog/metadata/dataの3階層であらわされるデータベース（テーブル）とファイル（メタファイルとデータファイル）の一式のことです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763657/c2b2295a-411c-be78-a9e2-ea767d10ac74.png)

https://iceberg.apache.org/spec/#overview


Icebergを理解するためのハンズオンが記載された神リポジトリ

https://github.com/lawofcycles/apache-iceberg-101-ja

# S3TablesとGlue Catalogの関係
Glue Catalogとの紐づけは以下のようになります
- テーブルバケット = カタログ
- 名前空間 = データベース
- テーブル　= テーブル

![image-19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763657/7730b2f4-9470-aa06-80c6-b31d16908157.png)

https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-tables-integrating-aws.html#how-table-integration-works

# S3 Tablesの作成
## テーブルバケットの作成
S3メニューにテーブルバケットという項目が増えていますのでここから「テーブルバケット作成」のボタンを押します
※　日本リージョンはまだ対応していないようですのでバージニアを使用します

https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-tables-regions-quotas.html

![image-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763657/ab0b7142-2802-100f-17fb-c37c5065306a.png)


s3-tables-tryというバケットを作りました。「統合を有効にする」にチェックを入れておきます

![image-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763657/12dfc606-0894-9e0e-3f2b-426521ee174e.png)



「統合を有効にする」にチェックを入れるとAWS Lake Formationテーブルバケットの登録とAWS Glueカタログの作成が実行されます

![image-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763657/259231ef-6ef7-2ddb-51a4-465f607554b8.png)



リージョンで1回行うと次回は有効になっています。

![image-6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763657/94a309c7-fe49-35fc-78ca-04610536c8e5.png)


数秒でできあがりました。削除はコンソールから実行できず、CLIなどから実行する必要があるようです。

![image-7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763657/b27a5e11-6cfa-72d5-6549-1d544325dae2.png)



出来上がったバケットをクリックするとテーブル一覧が表示されますが、コンソールからは何も操作できません。

![image-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763657/6f59c97f-e101-5cba-2caf-528362336c45.png)


## LakeFormationの設定
Get Startedボタンを押して利用を開始します

![image-12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763657/d2dd1892-881e-e039-fc4b-7d5f10a69a86.png)


DataCatalog > Catalogs Newを見るとs3tablescatalogが追加されています

![image-13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763657/d199595a-e161-7b3e-fc57-f27b7a821560.png)


s3tablescatalogを選択してActionsのGrant（権限付与）を押します

![image-14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763657/df572cb8-377d-eea5-aa29-d0cdb6782f85.png)


自身のIAMユーザに対してs3tablescatalogとs3tablescatalog/s3-tables-tryを選択して、SuperUser権限を与えました

![image-15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763657/b5fc43c3-8ff8-3c3b-3bdf-971b4705c941.png)

![image-17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763657/e6146679-3893-a649-4b39-b870134a8a9f.png)

![image-16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763657/e68ed1cb-4993-92bf-445a-766fd8cf3521.png)


## parquetファイルのデータをテーブルにロードしてみる
Cloudshellにsparkshellをインストールしてsparkから操作します
sparkを準備する手順はこちらの神記事で紹介されています

https://dev.classmethod.jp/articles/reinvent2024-amazon-s3-tables/

sparkが起動するとロゴが表示されsparkコマンドが使えるようになります

![image-20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763657/8d57728b-287c-fd6e-138a-8781a28b184d.png)


名前空間とテーブルを適当に作成します
```shell
spark.sql("create namespace if not exists s3tablesbucket.namespace_s3tables_try")
spark.sql("create table if not exists s3tablesbucket.namespace_s3tables_try.test_parqeut ( measurement_time timestamp, speed int, accel float, gas float) using iceberg ")
```

この時点でathenaを見るとでカタログ（テーブルバケット）やデータベース（名前空間）、テーブル（テーブル）が選択できるようになっています

![image-21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763657/ec902d5e-6bb4-78d5-1372-e76cf38fd9cf.png)


スキーマを合わせたparquetファイルを作成してテーブルにロードしてみます

pythonでparquetの作成
```python
import pandas as pd
import pyarrow.parquet as pq
import pprint
import numpy as np
from datetime import datetime, timedelta

# データフレームの行数を定義
num_rows = 10000

# measurement_time 列をカウントアップする値で生成 (ミリ秒単位)
measurement_time = [datetime.now() + timedelta(milliseconds=i*10) for i in range(num_rows)]

# 他の3つの列には適当に変化する値を生成
col_speed = (np.random.random(num_rows) * 100).astype(int)  # 0から100の範囲でランダムな値(int)
col_accel = np.random.random(num_rows)   # 0から1の範囲でランダムな値(float)
col_gas = np.random.normal(loc=50, scale=10, size=num_rows)  # 平均50、標準偏差10の正規分布に従うランダム値(float)

# データフレームの作成
df = pd.DataFrame({
    'measurement_time': measurement_time,
    'speed': col_speed,
    'accel': col_accel,
    'gas': col_gas,
})

print(df.head())
df.to_parquet('output.parquet',
              version = "2.6",      # default "2.6"
              row_group_size = 4000,   # default None(minimum of the Table size and 1024 * 1024.)
              data_page_size = 1024 * 1, # 1KB default None(1MB)
            )
```

parquetの読み込み
```shell
var df = spark.read.parquet("/home/cloudshell-user/output.parquet") // or s3 uri
```

sparkがnano分解能のtimestampをサポートしていなかったためint96 timestampで出力して再トライします
![image-22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763657/04110fc2-63f4-06c3-d09c-529f088cb8dd.png)


```python
df.to_parquet('output.parquet',
              version = "1.0",      # default "2.6"
              row_group_size = 4000,   # default None(minimum of the Table size and 1024 * 1024.)
              data_page_size = 1024 * 1, # 1KB default None(1MB)
              use_deprecated_int96_timestamps = True
            )
```

上手く読み込めました
![image-24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763657/6fa5ba35-367d-02b2-6970-6641efe4db24.png)


テーブルにロードして無事athenaでクエリすることができました
```shell
df.writeTo("s3tablesbucket.namespace_s3tables_try.test_parqeut").using("Iceberg").tableProperty("format-version", "2").createOrReplace()
```

![image-26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763657/af10130e-e35d-86aa-b63c-09f3a48ee68a.png)

# まとめ
s3 tablesを使用してIcebergに触れることができました。
今回の内容ではs3 tablesの利点やIcebergの利点については感じ取ることができなかったため、これらに注目した記事も書いてみたいと思います。

最後まで読んでいただきありがとうございました。本記事の内容に誤りなどあればコメントにてご教授お願いいたします。

# 参考
[データレイクの新しいカタチ：Open Table Formatの紹介](https://bering.hatenadiary.com/entry/2023/07/17/235246)
[AWSにおける Hudi/Iceberg/Delta Lake の使いどころと違いについて](https://pages.awscloud.com/rs/112-TZM-766/images/AWS-Black-Belt_2023_Datalake-Format-On-AWS_0516_v1.pdf)
[Apache Iceberg](https://iceberg.apache.org/)
[[アップデート]Apache Iceberg形式のテーブルデータに最適化されたストレージAmazon S3 Tablesが発表されました](https://dev.classmethod.jp/articles/reinvent2024-amazon-s3-tables/)