マイクロアドのData LakehouseとIcebergテーブルの最適化について

-- Views

June 17, 25

スライド概要

2025-06-17 OTFSG Osaka Meetup #01
https://otfsg-tokyo.connpass.com/event/352637/

profile-image

嫁と子供と酒で出来ているインフラエンジニア。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

(ダウンロード不可)

関連スライド

各ページのテキスト
1.

2025-06-17:OTFSG Osaka Meetup #01 #otfsg_tokyo マイクロアドのData Lakehouseと Icebergテーブルの最適化について 株式会社マイクロアド 永富 安和 ( @yassan168 )

2.

アジェンダ 1. マイクロアドのデータ基盤について 2. Icebergテーブルの概要 ちょっとだけ⋯ 3. Icebergテーブルの最適化に関する話 4. まとめ #trinodb

3.

マイクロアドについて

4.

マイクロアドが提供する事業 #trinodb 自社製品である 「データプロダクト」 と、 主に 他社製品を扱う 「コンサルティング」の二つの事業 ※2025年9月期 決算説明資料より抜粋

5.

データプロダクト「UNIVERSE」について #trinodb 膨大なデータから業界業種毎の消費行動プロセスの違いを分析することで、 特定の製品カテゴリ毎に比較検討を開始したユーザ、最終的な購買検討段階に 移行したユーザー、購買意欲が急激に高まった瞬間など、 個人の製品認知・購買プロセスの段階に応じたより効果的な広告配信を実現 ※1 2024年9月実績 ※2 RTB: Real Time Biddingの略称

6.

マイクロアドのデータ基盤について

7.

これまでのデータ基盤の概要 実効容量 約13T B/day を処理 ト イ バ タ 約2ペ 分析クエリ実行 参照 平均流量は Gbit/sec Impala 参照 分析用クラスタ Data Lake Streaming ログ転送 書き 込み バッチ 実行 HDFSをHiveの 定 Locationに指 広告配信サーバ 各種サーバ Spark Streaming (リアルタイム加工) Redis ワークフロー バッチ 実行

8.

今回の刷新範囲 分析クエリ実行 ココ! 参照 Impala 参照 分析用クラスタ Data Lake Streaming ログ転送 書き 込み バッチ 実行 バッチ 実行 広告配信サーバ 各種サーバ Spark Streaming (リアルタイム加工) Redis ワークフロー

9.

これまでのデータ基盤の課題 #trinodb 1. 利用しているHadoopディストリビューションが継続利用出来ない ○ 有償のCloudera CDPも検討したが費用面がクリア出来ず見送り (PaaSも色々検討したが費用や技術課題がクリア出来ず見送り。5年償却で見たクラウドは高い) 2. レプリカ3が結構にコストに直結 ○ 実効容量2PBということは、物理Diskで6PBは必要な現実 ○ Hadoop 3.x なら Erasure Coding もあるけど、 ディスク消費を抑えやすいが CPU 使用率が増える問題 3. 実質的に Compute と Storage を切り離してスケールしづらい ○ ○ NodeManager と DataNode は 同居が前提(データ局所性) → ノード追加=CPU+ディスク丸ごと増量 専用 Computeノードも構築可能だがデータ転送増&帯域ボトルネックで割に合わない

10.

これまでのデータ基盤の課題 4. Impalaの統計情報の運用が非常に煩雑かつ有効に利用出来ない ○ ワイドカラム・大量データの大規模テーブルの場合、ほぼ使えない (inc_stats_size_limit_bytesを大きく設定できない問題) ○ 統計情報が不十分なので効率の悪いクエリになりがち 5. ETL/ELT処理は Hive on MapReduce なので確実に処理するが遅い ○ Tez・LLAPを使うべきだがHiveが古くて利用できない 6. Hive Metastore が大量パーティションで悲鳴を上げる ○ 大量の DROP PARTITION / DROP TABLE が メタストア DB ロックを長時間保持 ○ Metastore Thrift サーバーが フル GC → 接続タイムアウト を誘発 7. テーブル構造が複雑でSQLベースでETL/ELT処理するのが辛い ○ 複雑なクエリになりがちで、改修に難易度が高く手間がかかる #trinodb

11.

新しいデータ基盤に求める事 #trinodb 1. ComputeとStorageを分離できる事 2. ETL/ELT処理はSQLだけでなくコードベースで処理したい 3. 大規模なテーブルでも統計情報を更新・有効活用できる事 4. Hiveの様にオンラインで柔軟なスキーマ進化が可能である事

12.

MDP(Microad Data Platform) Overview 分析クエリ REST Catalog Server R/W (Active/Standby) (Active/Standby) 管理 管理 Sparkアプリをapply Spark Connect で接続して処理 R/W DELL ECS(S3互換ストレージ) Sparkアプリの リソース制御

13.

ざっくりな使い分け Ad-hoc クエリ Business User Engineer Digdag Redash Trino Gateway Spark Connect Client Trino on K8s Coordinator Iceberg REST Catalog ETL/ELT処理 Gateway Layer Spark Operator Spark on K8s Worker SparkApp SparkApp Catalog Layer Spark Connect Server SparkApp SparkApp SparkApp Dell ECS(S3-Compatible) Compute Layer Storage Layer

14.

得られた効果

15.

得られた効果 #trinodb 1. ComputeとStorageを分離出来る事 😆 ComputeはSpark・Trino、Storageは S3互換ストレージと明確に分離出来た 😆 YARN・Zookeeperの依存がなくなり構成要素が減った 2. ETL/ELT処理はSQLだけでなくコードベースで処理したい 😆 SQLでは表現しづらい複雑なビジネスロジックをPySparkのDataFrame APIで 柔軟に実装可能になった

16.

得られた効果 #trinodb 3. 大規模なテーブルでも統計情報を更新・有効活用ができる事 😆 Icebergテーブルを使うことで、Impalaで問題になっている統計情報が作れな い問題が発生しない ■ パーティション情報といった基本的な統計情報は テーブル書き込み時に意識せずに作成が可能になった ■ 実行エンジン側で柔軟に統計情報の更新も可能 4. Hiveの様にオンラインで柔軟なスキーマ進化が可能である事 😆 Iceberg特有のスキーマ進化(or パーティション進化)により、 以前より柔軟な運用が選択可能になった

17.

Apache Icebergテーブルについて

18.

Apache Icebergとは #trinodb Netflix、Apple、Tencent、Pinterest などの多くの大企業も本番利用している 巨大で複雑なデータセットにも対応可能なOpen Table Formatの1つ。 特徴 ● 複数のエンジンから同じテーブルを操作できる ○ 例:Spark・Flink・Trino・Presto・Hive・Impala ● 安全なテーブル操作 ○ ACIDなコミット(Atomic Commit + Optimistic Concurrency) ● スキーマ/パーティション進化 ○ 既存データをコピーせず列やパーティションを変更 ● Hidden Partitioning ○ パーティションを意識せずにクエリしたり、パーティションレイアウトを変更可能 ● タイムトラベル/ロールバック ○ “過去の状態” でクエリ/ロールバックが可能

19.

”Iceberg”=仕様だけ ― 実装はエンジン & SDK が担 #trinodb 当 言わば、Iceberg はデータ版 HTTPの様なもの。 ● HTTP:プロトコル仕様だけを定め、Apache/Nginx/Chrome が実装 ● Iceberg:テーブル仕様 だけを定め、Spark/Trino/PyIceberg … が実装 つまり、、、 「”Iceberg” という振る舞い」をテーブル仕様に基づき独自に実装 テーブル仕様は公開され・コミュニティで仕様を決定しているので、 様々な実行エンジン&各言語SDKによりIcebergテーブルに対して操作が可能。 =ベンダーロックインがない。 とは言え、、 きたけど、 ダーロックインは回避で ン ベ eの ut mp Co eや ag or St ようとする動きが、、、 し ン イ ク ッ ロ で グ ロ タ 今度は各社カ

20.

Icebergテーブルのアーキテクチャ 大きく3つに構成されます。 1. カタログ 2. メタデータ層 3. データ層 今回は、3→2→1 の順で、 下から順に説明していきます。 #trinodb

21.

⚠ ここから先は Icebergテーブル仕様 v2 を前提とします すま ん... 尺が足りないので割愛。 以下で説明してます! Icebergテーブルの内部構造について - やっさんメモ

22.

Icebergテーブル:データ層 以下の3つで構成 ● ● ● データファイル 削除ファイル Puffinファイル データ層は、分散ファイルシステム(HDFS) や、オブジェクトストレージに対応。 #trinodb

23.

Icebergテーブル:データ層 データファイル テーブルのデータそのもの。 Parquet・Avro・Orcに対応。 ※同一テーブル中に複数のフォーマットでもアリ 削除ファイル テーブルのデータセット内の どのレコードが 削除されたかを追跡する際に利用。 以下の2種類の戦略がある。 ● Copy-On-Write(CoW) ● Merge-On-Read(MoR) #trinodb

24.

Icebergテーブル:データ層 Puffinファイル データファイルやメタデータファイルに格 納される統計情報よりも更に 幅広いクエリのパフォーマンスを 向上させるデータテーブル内のデータに関 する統計情報とインデックスを格納。 #trinodb

25.

Icebergテーブル:メタデータ層 以下の3つで構成され、ツリー構造になっ ている。 ● メタデータファイル ● マニフェストリスト ● マニフェストファイル #trinodb

26.

Icebergテーブル:メタデータ層 マニフェストファイル データ層内のファイルの追跡と、各ファイルに 関する追加の詳細や統計情報を保持。 1つのマニフェストファイルには、 1つの「データファイル」または 「削除ファイル」のみが含まれる。 各マニフェストファイルには、以下が含まれる ● ● ● ● パーティションのどこに紐づいているか レコード数 カラムの上限と下限値 etc.. は ファイル名 o r ***.av #trinodb

27.

Icebergテーブル:メタデータ層 マニフェストリスト マニフェストファイルのリスト。 マニフェストリストには、テーブルのある時点での 状態である「スナップショット」を構成する 各マニフェストファイルに関する情報がある。 は その情報には、以下が含まれます。 ファイル名 vr a . * * snap-* ● マニフェストファイルのパス o ● どのスナップショットの一部として 追加されているのか ● 属するパーティションに関する情報 ● 追跡するデータファイルのパーティション列の 下限と上限値 #trinodb

28.

Icebergテーブル:メタデータ層 メタデータファイル テーブルに関するメタデータを格納し、 以下の情報が含まれています。 ● テーブルのスキーマ ● パーティション情報 ● スナップショット ● 現状のスナップショットはどれか Icebergテーブルが変更されるたびに 新しいメタデータファイルが作成されます。 また、次に説明するカタログにより アトミックに最新版のメタデータファイル として登録される。 は ファイル名 .jso ta a d a t v⃝.me n または m-uuid> ⃝-<rando .json .metadata #trinodb

29.

Icebergテーブル:カタログ カタログ 最新のメタデータがどこにあるかを保持する。 その為、カタログの主な要件は、現在のメタ データのポインタを更新するためのアトミック な操作をサポートすることにある。 カタログには色々な実装があります。 ● Hadoopカタログ(File System) ● Hiveカタログ ● Nessieカタログ ● AWS Glueカタログ ● RESTカタログ #trinodb

30.

メタデータ層の動きについて な の話 ここ

31.

テーブル作成直後(おさらい) カタログ ice.tbl1 メタデータ層 データ層 metadata file manifest list manifest v⃝.metadata.json snap-***.avro ***.avro v1.json ● テーブルのスキーマ情報 ● パーティション情報 ● スナップショット情報や履歴 ● テーブルのプロパティ メタデータファイル =「テーブル全体の現行設定とスナップショット一覧を管理」 💡カタログで管理してるのは、こいつの最新のパス スナップショット =「ある時点でのテーブルの全体像(= データファイルの集合)」 💡タイムトラベル/ロールバック単位。

32.

メタデータはツリー構造(おさらい) カタログ ice.tbl1 メタデータ層 データ層 metadata file manifest list manifest v⃝.metadata.json snap-***.avro ***.avro v1.json s1 Snapshotの s m1 stats m1 A metadata A.parquet B metadata ● マニフェストファイルのパス ● どのスナップショットの一部として追加されているのか ● 属するパーティションに関する情報 ● 追跡するデータファイルのパーティション列の下限と上限値 B.parquet マニフェストリスト =「スナップショットが参照するマニフェストファイルのリスト」 💡このスナップショットに何が入ってるの?を高速に把握

33.

メタデータはツリー構造(おさらい) カタログ ice.tbl1 メタデータ層 データ層 metadata file manifest list manifest v⃝.metadata.json snap-***.avro ***.avro v1.json manifest の m s1 m1 stats m1 A metadata A.parquet B metadata B.parquet ● パーティションのどこに紐づいているか ● レコード数 ● カラムの上限と下限値 マニフェストファイル =「データファイルのメタ情報(パーティション値・列統計など)」 💡スキャン計画時に不要ファイルを即座に除外

34.

データを挿入(書き込み中) カタログ ice.tbl1 メタデータ層 データ層 metadata file manifest list manifest v⃝.metadata.json snap-***.avro ***.avro v1.json s1 m1 stats m1 A metadata A.parquet B metadata B.parquet C metadata C.parquet s1 v2.json s2 m1 stats m2 stats m2 まずは実行エンジン側で書き込 み

35.

データを挿入(書き込み完了) カタログ ice.tbl1 メタデータ層 metadata file manifest list manifest v⃝.metadata.json snap-***.avro ***.avro v1.json s1 m1 stats 最新のパス を切り替え ice.tbl1 データ層 m1 A metadata A.parquet B metadata B.parquet C metadata C.parquet s1 v2.json s2 m1 stats m2 stats m2 📑同時書き込み制御の話はべりんぐさんのブログがとても参考になりま す! Apache Icebergの同時実行制御における競合解決の仕組みと注意点 https://bering.hatenadiary.com/entry/2025/01/18/234339

36.

Icebergテーブルのメンテナンス

37.

なぜIcebergテーブルにメンテナンスが必要? よくある “詰まり” シナリオ ● 高頻度アップサート/ストリーム書込み → マニフェストが爆増して プランニング遅延 ● 小さなバッチ Insert が続く → “Smallファイル問題” で I/O 効率 ↓↓↓ ● 分析速度を上げるための並べ替え(ソート/クラスタリング) → 既存ファイルを再編成しない限り効果が出ない 放置すると... 1. Query Planning が遅い ○ 1 クエリで数千 manifest を開く事態も 2. スキャン効率 & 物理コストが悪化 ○ S3 API コール増加 / キャッシュ不可ファイル増殖 #trinodb

38.

なぜIcebergテーブルにメンテナンスが必要? よくある “詰まり” シナリオ ● 高頻度アップサート/ストリーム書込み → マニフェストが爆増して プランニング遅延 ● 小さなバッチ Insert が続く → “Smallファイル問題” で I/O 効率 ↓↓↓ なので、メタデータ層・データ層において ● 分析速度を上げるための並べ替え(ソート/クラスタリング) → 既存ファイルを再編成しない限り効果が出ない ”定期的なお掃除”が必要! 放置すると... 1. Query Planning が遅い ○ 1 クエリで数千 manifest を開く事態も 2. スキャン効率 & 物理コストが悪化 ○ S3 API コール増加 / キャッシュ不可ファイル増殖 #trinodb

39.

📑以降はSparkのプロシージャを例に解説していきま す データ層の整理

40.

データファイルのCompaction(rewrite_data_files) カタログ メタデータ層 データ層 metadata file manifest list manifest rewrite_data_files前 v⃝.metadata.json snap-***.avro ***.avro データサイズがとて も小さい v1.json or ソートされてないとする s1 m1 stats m1 ice.tbl1 A metadata A.parquet B metadata B.parquet C metadata C.parquet s1 v2.json s2 m1 stats m2 stats m2

41.

データファイルのCompaction(rewrite_data_files) カタログ メタデータ層 metadata file manifest list rewrite_data_files後 v⃝.metadata.json snap-***.avro v1.json s1 データ層 manifest ファイルの最適化 ***.avro (小さいファイルをまとめた り、ソートし直す) m1 stats m1 A metadata A.parquet B metadata B.parquet C metadata C.parquet s1 v2.json s2 m1 stats m2 stats m2 s1 ice.tbl1 v3.json s2 s3 m2 stats NEW m3 stats m3 NEW AB metadata AB.parquet

42.

データファイルのCompaction #trinodb 行レベル削除ファイル(Position Delete)のコンパクション rewrite_position_delete_files ● merge-on-read (MoR) モードで増える小粒 Position Delete をま とめ、 ○ 目標サイズ(既定 64 MB)で再書き出し ○ 「宙ぶらりん(dangling)」 な delete 行も同時に除去 ● Position Deleteは Icebergテーブル仕様 v3 では非推奨 (新規追加は禁止/既存は読み取り可) ○ 後継は Deletion Vectors (DV)

43.

日時キー付きテーブルの「不要データ」の削除 #trinodb 勘違いしやすいやつの1つ。 スナップショットを期限切れにしたところでレコードは削除されない。 Iceberg は 自動 TTL を持たない。 古データを捨てるには 2 段階で手動実行が必要 1. DELETE文で論理削除 ○ -- 90日前のレコードは削除 DELETE FROM c.ns.tbl WHERE event_date < date_sub(current_date, 90); 2. Deleteされて紐づかなくなったスナップショットをexpire_snapshotsする ことでファイルを物理削除

44.

メタデータ層の整理

45.

メタデータのCompaction(expire_snapshots) カタログ メタデータ層 metadata file manifest list v⃝.metadata.json snap-***.avro v1.json データ層 expire_snapshots前 manifest ***.avro s1 m1 stats m1 A metadata A.parquet B metadata B.parquet C metadata C.parquet AB metadata AB.parquet s1 v2.json v3 の時点では s1 や s2 に戻ることは出来る ice.tbl1 v3.json 最新は こっち s2 m1 stats m2 stats m2 s1 s2 m2 stats s3 m3 stats m3

46.

メタデータのCompaction(expire_snapshots) カタログ メタデータ層 metadata file manifest list v⃝.metadata.json snap-***.avro v1.json データ層 expire_snapshots後 manifest ***.avro s1 m1 stats m1 s1 v2.json s2 A metadata A.parquet B metadata B.parquet C metadata C.parquet AB metadata AB.parquet m1 stats s1 v3.json s1やs2を期限切れに して整理する m2 stats s2 m2 s3 m2 stats ice.tbl1 v4.json s3 m3 stats m3

47.

メタデータのCompaction(expire_snapshots) カタログ メタデータ層 データ層 metadata file manifest list manifest v⃝.metadata.json snap-***.avro ***.avro v1.json s1 m1 stats m1 s1 v2.json s2 A metadata A.parquet B metadata B.parquet C metadata C.parquet AB metadata AB.parquet m1 stats s1 v3.json m2 stats s2 m2 s3 m2 stats ice.tbl1 v4.json s3 m3 stats m3

48.

メタデータファイルはいつ消えるの? カタログ メタデータ層 データ層 metadata file manifest list manifest v⃝.metadata.json snap-***.avro ***.avro v1.json s1 s1 v2.json s2 テーブルプロパティを以下にするとメタデータファイルを削除 出来る(=指定しないと消えない) 'write.metadata.delete-after-commit.enabled' = 'true' 最大何個 残すかは以下で指定 ‘write.metadata.previous-versions-max’ = 100 s1 v3.json m2 s2 C metadata C.parquet AB metadata AB.parquet s3 m2 stats ice.tbl1 v4.json s3 m3 stats m3

49.

メタデータのCompaction(rewrite_manifests) カタログ メタデータ層 metadata file manifest list v⃝.metadata.json snap-***.avro データ層 rewrite_manifests manifest 前 ***.avro m2 ice.tbl1 v4.json m3 stats C.parquet 例えば、どちらも同じ パーティション内のデータ m2 stats s3 C metadata m3 AB metadata AB.parquet

50.

メタデータのCompaction(rewrite_manifests) カタログ メタデータ層 metadata file manifest list manifest v⃝.metadata.json snap-***.avro ***.avro m2 データ層 rewrite_manifests 後 C metadata C.parquet AB metadata AB.parquet m2 stats v4.json ice.tbl1 v5.json s3 m3 stats s3 s4 m3 m4 m4 stats ABC metadata データはそのままで 新しいマニフェストから どちらのデータも指す

51.

他にも、、 #trinodb 孤立ファイルのクリーンアップ (remove_orphan_files) ● ● ● メタデータに参照されない orphan file (孤児ファイル)を物理削除 既定で 3 日より古いファイルのみ 対象 older_than でデフォルト「3日」から調整は可能 注意点 ● 別でデータ書き込み中のファイルが remove_orphan_files の 対象期間中だった場合、誤って削除する可能性があるので、ワークロードに 合わせた older_than の設定が重要 ○ 実行時には戻り値の orphan_file_location をログ出力しておいて、何を削除したのか追跡可 能にしておくと安心

52.

問題です!!! どの順で実行するとコスパが良いでしょうか? 1. delete from c.ns.tbl where ** (要らなくなったレコードの削除) 2. remove_orphan_files 3. rewrite_data_files 4. rewrite_position_delete_files 5. rewrite_manifests 6. expire_snapshots #trinodb

53.

正解は、、、 (多分だけどな) #trinodb 1. 古いレコードの削除 delete from c.ns.tbl where ** 2. データの最適化(適正サイズ化・ソート) rewrite_data_files / rewrite_position_delete_files 3. マニフェストの最適化 rewrite_manifests 4. スナップショットをGC づく 期限切れのスナップショットに紐 削除 要らないデータやマニフェストも expire_snapshots 5. メタデータのどこにも紐づいていないファイルの削除 remove_orphan_files

54.

注意点 ● #trinodb データまたはメタデータを整理に伴い、コミットが発生するので、 対象が他メンテナンス処理含む全てのデータ更新と重複させない対策が必要 (被ったらプロシージャが失敗する) 例えば、以下のプロシージャのパラメータ older_than を使って対象期間を 指定出来る ○ remove_orphan_files(孤児ファイルの削除) ○ expire_snapshots (スナップショットのGC) ● データのソートやクラスタリングは慎重に選ばないと、デカいテーブル 程、やり直しにはかなりの計算コストが必要になるので注意して検討が 必要

55.

悩みのタネ — みなさんどうしてますか? カラムの統計ってどうしてますか? min / max / null_count はテーブル書き込み時に作成済みだけど、、 compute_table_stats 使ってNDV (distinct_count)作成してますか? ● ● かなりの計算コストを支払うので、そこまでやるメリットがどれくらいあるのか? ○ 追記/削除が多いテーブルはNDV が陳腐化するから再計算も必要になる 高カーディナリティになりがちのSTRING型カラムは含めてますか? Icebergテーブルの最適化のタイミングの決定する基準は? ● 一律 1日1回とか? ● remove_orphan_files と expire_snapshots の older_than ってどうしてます? #trinodb

56.

まとめ

57.

まとめ ● マイクロアドのData Lakehouse事例紹介 ● Icebergテーブルについて ● Icebergテーブルの最適化について #trinodb

58.

参考 ● #trinodb Apache Iceberg とは何か - Bering Note – formerly 流沙河鎮 https://bering.hatenadiary.com/entry/2023/09/24/175953 ● Icebergテーブルの内部構造について - やっさんメモ https://yassan.hatenablog.jp/entry/advent-calendar-2023-1201 ● Apache Iceberg's Best Secret: A Guide to Metadata Tables - YouTube https://www.youtube.com/watch?v=4K0HBWUIEKI ● Apache Icebergの同時実行制御における競合解決の仕組みと注意点 - Bering Note – formerly 流沙河鎮 https://bering.hatenadiary.com/entry/2025/01/18/234339 ● Lakehouse テーブル形式とカタログの活用 | データエンジニアリング オープ ンフォーラム 2025 - YouTube https://youtu.be/wLsMrdfBuwU?si=4lrh8Ds2HV6EmN7s ● ダ鳥獣戯画 https://chojugiga.com/