Icebergのデータ層にs3a使わずにOzoneでデータ分析に最適なofs/FSOを使いたい話

この記事は MicroAd Advent Calendar 2023Distributed computing (Apache Spark, Hadoop, Kafka, ...) Advent Calendar 2023 の25日目の記事です。

アドカレも今日で最後になりました。会社の方は全部埋まって記事も投稿出来てるので一安心ですが、Distributed computing アドカレの方は寂しい状態です。とは言え、参加してくれた皆さんには感謝しかないし、どの記事も良かった!
ただ、せっかくなので、出来るだけ空いてるところを埋めていきたいところ。

では、今日のお題に入っていきます。

前提

TL;DR

前ふり

Storage User Group Meetup というイベント(下はそのアーカイブ)でOzoneやIcebergを取り上げていて、そこで、Icebergのデータ層にS3じゃなくて、Apache Ozoneのofsを利用できるよって教えてもらいました2

youtu.be

その記事がこちら。

community.cloudera.com

Ozoneについては以下に関連情報をまとめてるので、そちらを参照ください(追加の情報もあれば是非お待ちしています)。

qiita.com

ofs?FSO?🙃

まずは、FSOについて。

Ozoneは、データの格納先であるOzoneバケットのレイアウトについて、ファイルシステム最適化(FSO)またはオブジェクトストア(OBS)の両方に対応しており、ファイルまたはオブジェクトに適したバケットにレイアウトをサポートしてます。

ファイルシステムとオブジェクトストアは下図のようにデータのメタデータの管理方法が異なります。
オブジェクトストアはファイルシステムに比べて、再帰的なディレクトリ削除やディレクトリ移動、リネームの操作は高価になってしまう3ので、データ分析の場合、ファイルシステムが適しています。なので、データ分析でOzoneを利用するならFSOのレイアウトを選択するのが適しています。

次に、ofsについて。

Ozoneは、データにアクセスするために、以下の複数のインターフェイスに対応しています。

例えば、ロケーションにofsを指定したHiveテーブルに対してaws s3 CLIからデータを読み込んだり、aws s3 CLIでデータを書き込んでofsつかってHiveテーブルで読み込むということも出来ます。

この辺りについては、以下のブログを参照ください。

blog.cloudera.com

ただ、Icebergテーブルを使う場合、たいていデータ層にs3を使うので、S3 Gatewayが間に入ることや、S3プロトコルを使う際のリネームや削除のアトミックに関する課題があるので、ofsを使う事でそれらの課題を解決出来ます。

Icebergでofs/FSOは使えるのか?

ofsは利用できるかについて。

以下の記事では、ClouderaのCDPでは、HiveからIcebergテーブルを利用しています。 この場合、Hive Metasoreが必要になります。既存でHadoopクラスタがある場合は良いですが、これだけのために、Hadoopクラスタを構築するのは微妙です。

community.cloudera.com

RESTカタログを使いたいところですが、その場合、ofsプロトコルにおけるIcebergテーブルに対するメタデータやデータの操作といったIcebergネイティブな対応が必要です。

実際にIcebergの実装を見るに、ofsの対応はありません。

github.com

ただ、その辺りの実装については、Iceberg側は柔軟に対応出来るように設計されているようです。また、Ozone ofsはHadoop互換ファイルシステムであるので、HadoopFileIOを拡張する事で対応が出来るらしい。IcebergのFileIOについては、以下のブログに詳しく説明されています。

tabular.io

OzoneのJIRAを確認したところ、 HDDS-7629 にて、FileIOについて検討するIssueがあったが、Ozone v1.4.0をターゲットにしているものの動きはなさそう。誰か実装してくれないものか…。私のJava力の無さよ😢

FSOレイアウトは利用できるかについて。

以下のドキュメントにあるように、デフォルトで作成されるS3互換のボリュームのバケットとして、別のボリュームにあるFSOレイアウトのバケットに対してリンクする事が出来ます。

ozone.apache.org

FSOレイアウトのバケットを作成して、S3互換のバケットとしてリンクさせてみます。

# FSOレイアウトのバケット /ozone-vol/ofs-bucket を作成
bash-4.2$ ozone sh bucket create --layout FILE_SYSTEM_OPTIMIZED /ozone-vol/ofs-bucket
bash-4.2$ ozone sh bucket info /ozone-vol/ofs-bucket
{
  "metadata" : { },
  "volumeName" : "ozone-vol",
  "name" : "ofs-bucket",
  "storageType" : "DISK",
  "versioning" : false,
  "usedBytes" : 0,
  "usedNamespace" : 0,
  "creationTime" : "2023-12-24T18:54:04.099Z",
  "modificationTime" : "2023-12-24T18:54:04.099Z",
  "quotaInBytes" : -1,
  "quotaInNamespace" : -1,
  "bucketLayout" : "FILE_SYSTEM_OPTIMIZED",
  "owner" : "hadoop",
  "link" : false
}

# S3互換のバケット/s3v/common-bucket として、/ozone-vol/ofs-bucket にリンクする
bash-4.2$ ozone sh bucket link /ozone-vol/ofs-bucket /s3v/common-bucket
bash-4.2$ ozone sh bucket info /s3v/common-bucket
{
  "volumeName" : "s3v",
  "bucketName" : "common-bucket",
  "sourceVolume" : "ozone-vol",
  "sourceBucket" : "ofs-bucket",
  "creationTime" : "2023-12-24T18:55:26.121Z",
  "modificationTime" : "2023-12-24T18:55:26.121Z",
  "owner" : "hadoop"
}

実際に、aws s3 CLIを使って、/s3v/common-bucket に対してデータを書き込んでみます。

❯ aws s3 cp --endpoint-url http://s3gw.internal:9878 --profile ozone ./test.csv s3://common-bucket/
upload: ./test.csv to s3://common-bucket/test.csv  

❯ aws s3 ls --endpoint http://s3gw.internal:9878 --profile ozone s3://common-bucket
2023-12-25 04:00:47          7 test.csv

FSOレイアウトのバケット /ozone-vol/ofs-bucket/ 側を見てみると書き込まれていることが分かります。

bash-4.2$ ozone fs -ls ofs:///ozone-vol/ofs-bucket/
Found 1 items
-rw-rw-rw-   1 hadoop hadoop          7 2023-12-24 19:00 ofs:///ozone-vol/ofs-bucket/test.csv

どうやらうまく行きそうです。 試しに、 s3://common-bucket を指定してIcebergテーブルを作成してデータを追加してみたら以下の様に、OFS側のボリュームのバケットにデータが書き込まれてました。

bash-4.2$ ozone fs -ls ofs:///ozone-vol/ofs-bucket/test_ofs_db/example_table_ice/
Found 2 items
drwxrwxrwx   - hadoop hadoop          0 2023-12-24 19:33 ofs:///ozone-vol/ofs-bucket/test_ofs_db/example_table_ice/data
drwxrwxrwx   - hadoop hadoop          0 2023-12-24 19:32 ofs:///ozone-vol/ofs-bucket/test_ofs_db/example_table_ice/metadata
bash-4.2$ ozone fs -ls ofs:///ozone-vol/ofs-bucket/test_ofs_db/example_table_ice/data/
Found 8 items
-rw-rw-rw-   1 hadoop hadoop       2362 2023-12-24 19:33 ofs:///ozone-vol/ofs-bucket/test_ofs_db/example_table_ice/data/00000-0-9acf600a-988a-427b-9bc9-c85526f504e7-00001.parquet
-rw-rw-rw-   1 hadoop hadoop       2347 2023-12-24 19:33 ofs:///ozone-vol/ofs-bucket/test_ofs_db/example_table_ice/data/00001-1-9acf600a-988a-427b-9bc9-c85526f504e7-00001.parquet
-rw-rw-rw-   1 hadoop hadoop       2374 2023-12-24 19:33 ofs:///ozone-vol/ofs-bucket/test_ofs_db/example_table_ice/data/00002-2-9acf600a-988a-427b-9bc9-c85526f504e7-00001.parquet
-rw-rw-rw-   1 hadoop hadoop       2374 2023-12-24 19:33 ofs:///ozone-vol/ofs-bucket/test_ofs_db/example_table_ice/data/00003-3-9acf600a-988a-427b-9bc9-c85526f504e7-00001.parquet
-rw-rw-rw-   1 hadoop hadoop       2362 2023-12-24 19:33 ofs:///ozone-vol/ofs-bucket/test_ofs_db/example_table_ice/data/00004-4-9acf600a-988a-427b-9bc9-c85526f504e7-00001.parquet
-rw-rw-rw-   1 hadoop hadoop       2362 2023-12-24 19:33 ofs:///ozone-vol/ofs-bucket/test_ofs_db/example_table_ice/data/00005-5-9acf600a-988a-427b-9bc9-c85526f504e7-00001.parquet
-rw-rw-rw-   1 hadoop hadoop       2368 2023-12-24 19:33 ofs:///ozone-vol/ofs-bucket/test_ofs_db/example_table_ice/data/00006-6-9acf600a-988a-427b-9bc9-c85526f504e7-00001.parquet
-rw-rw-rw-   1 hadoop hadoop       2375 2023-12-24 19:33 ofs:///ozone-vol/ofs-bucket/test_ofs_db/example_table_ice/data/00007-7-9acf600a-988a-427b-9bc9-c85526f504e7-00001.parquet
bash-4.2$ ozone fs -ls ofs:///ozone-vol/ofs-bucket/test_ofs_db/example_table_ice/metadata/
Found 4 items
-rw-rw-rw-   1 hadoop hadoop       1856 2023-12-24 19:32 ofs:///ozone-vol/ofs-bucket/test_ofs_db/example_table_ice/metadata/00000-63752b02-b1f9-4756-a10d-d41c8a2bdddd.metadata.json
-rw-rw-rw-   1 hadoop hadoop       2955 2023-12-24 19:33 ofs:///ozone-vol/ofs-bucket/test_ofs_db/example_table_ice/metadata/00001-c7c5371c-d685-4916-a0fa-83bfc320c30f.metadata.json
-rw-rw-rw-   1 hadoop hadoop       7715 2023-12-24 19:33 ofs:///ozone-vol/ofs-bucket/test_ofs_db/example_table_ice/metadata/30598e1e-0b27-454c-9ae7-e6cd26d2a318-m0.avro
-rw-rw-rw-   1 hadoop hadoop       4273 2023-12-24 19:33 ofs:///ozone-vol/ofs-bucket/test_ofs_db/example_table_ice/metadata/snap-6515350754690444878-1-30598e1e-0b27-454c-9ae7-e6cd26d2a318.avro

補足)お試し環境 ※ここを押下するとサンプルの環境が出てきます

compose.yaml

---
version: "3"

services:
  spark-iceberg:
    image: tabulario/spark-iceberg:3.5.0_1.4.2
    networks:
      iceberg_net:
    depends_on:
      - iceberg-rest
    volumes:
      - ./spark-conf:/opt/spark/conf
    ports:
      - 8888:8888
      - 8080:8080
      - 4040:4040
      - 10000:10000
      - 10001:10001
    environment:
      - AWS_ACCESS_KEY_ID=any
      - AWS_SECRET_ACCESS_KEY=any
      - AWS_REGION=us-east-1
  iceberg-rest:
    image: tabulario/iceberg-rest:0.12.0
    networks:
      iceberg_net:
    ports:
      - 8181:8181
    environment:
      - AWS_ACCESS_KEY_ID=any
      - AWS_SECRET_ACCESS_KEY=any
      - AWS_REGION=us-east-1
      - CATALOG_WAREHOUSE=s3a://common-bucket
      - CATALOG_IO__IMPL=org.apache.iceberg.aws.s3.S3FileIO
      - CATALOG_S3_ENDPOINT=http://s3gw.internal:9878
      - CATALOG_S3_PATH__STYLE__ACCESS=true
networks:
  iceberg_net:

spark-defaults.conf

spark.sql.session.timeZone         Asia/Tokyo
spark.sql.extensions               org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions
spark.sql.hive.metastore.jars      maven

spark.sql.defaultCatalog           rest

spark.eventLog.enabled             true
spark.eventLog.dir                 /home/iceberg/spark-events
spark.history.fs.logDirectory      /home/iceberg/spark-events
spark.sql.catalogImplementation    in-memory

spark.hadoop.fs.s3a.access.key      any
spark.hadoop.fs.s3a.secret.key      any

# REST Catalog
spark.sql.catalog.rest                org.apache.iceberg.spark.SparkCatalog
spark.sql.catalog.rest.type           rest
spark.sql.catalog.rest.uri            http://iceberg-rest:8181

spark.sql.catalog.rest.warehouse      s3a://common-bucket
spark.sql.catalog.rest.io-impl        org.apache.iceberg.aws.s3.S3FileIO
spark.sql.catalog.rest.s3.endpoint    http://s3gw.internal:9878
spark.sql.catalog.rest.s3.path-style-access  true

Ozone側は、Apache OzoneのバイナリをDLして、 ozone-1.3.0/compose/ozone にあるdocker-compose.yamlを利用。

# ozone-1.3.0/compose/ozone に移動後に実施
# 一旦、各コンポーネントを起動
./run.sh -d

# DataNodeだけ、3つまでスケール
export OZONE_REPLICATION_FACTOR=3
   ./run.sh -d

最後に

いかがだったでしょうか。

Storage User Group Meetupで教えてもらった記事を読んで「いけるんじゃね?」って安直に考えたのは失敗でした。。。
HDDS-7629Ozone FileIOが爆誕するのを心待ちにしてます。

以上、MicroAd Advent Calendar 2023](https://qiita.com/advent-calendar/2023/microad) と Distributed computing Advent Calendar 2023 の25日目の記事でした。


  1. IcebergのFileIOについては、こちらがまとまってます
    tabular.io
  2. OzoneやIceberg以外にもHDFSSSDキャッシュの話も興味深いです。
  3. オブジェクトストアは、Key-Valueメタデータを管理しているのでディレクトリやリネームには適しておらず、コピーと削除の2つの操作に分かれるのでアトミックではないので。