はじめに
自宅で構築しているログ収集基盤では、分析用データの保存形式として Apache Iceberg を利用しています。
現在は主に次の構成で、HDFS 上の Iceberg テーブルを Spark や Trino から参照しています。
HDFS
↓
Apache Iceberg
↓
Spark / Trino
今回はこの自宅 Iceberg をそのまま捨てて AWS へ移行するのではなく、AWS 側に分析用コピーを作ります。
自宅Iceberg
↓
Amazon S3
↓
AWS Glue Data Catalog
↓
Amazon Athena
設計の基本方針は次の通りです。
自宅 = メイン環境 / Source of Truth
AWS = 分析用コピー
自宅側の HDFS + Iceberg は引き続き正本として残し、AWS 側には Athena で分析しやすい Iceberg テーブルを別に作ります。
なお、本記事では個人環境固有の値はプレースホルダーで記載します。
<YOUR_ICEBERG_BUCKET>
<YOUR_ATHENA_RESULT_BUCKET>
<YOUR_AWS_ACCOUNT_ID>
<YOUR_AWS_PROFILE>
本記事は、構成・実装方法の整理を目的として作成したもので、現時点では記載内容について実環境での一連の動作検証は実施していません。
可能な範囲で公式ドキュメント等を参照して記載していますが、環境や各サービスのバージョンによっては、手順どおりに動作しない可能性があります。
今後、実機で検証する機会があれば、確認できた内容や修正点を記事へ反映する予定です。
前提環境
自宅側には、以下の環境がすでに存在しているものとします。
| 項目 | 値 |
|---|---|
| OS | Ubuntu 24.04 |
| Hadoop | 3.3.6 |
| HDFS | HA 構成 |
| HDFS nameservice | cluster1 |
| Hive | 3.1.3 |
| Spark | 3.5.8 |
| Scala | 2.12 |
| Apache Iceberg | 1.10.1 |
| Iceberg runtime | iceberg-spark-runtime-3.5_2.12-1.10.1.jar |
| 自宅側 catalog | hive_prod |
| 自宅側 warehouse | hdfs://cluster1/warehouse/iceberg |
今回 AWS へ複製する対象は、次の2テーブルです。
hive_prod.logs.syslog_iceberg
hive_prod.logs.authlog_iceberg
AWS 側には、次の Iceberg テーブルを作ります。
glue_prod.logs.syslog_iceberg
glue_prod.logs.authlog_iceberg
既存記事上では主なカラム例として以下を使っています。
host
ts
severity
program
msg
dt
hr
ただし、実際に構築するときは syslog_iceberg と authlog_iceberg の schema が完全に同じであると決め打ちしません。構築編では DESCRIBE TABLE や SHOW CREATE TABLE で両方を確認してから AWS 側テーブルを作成します。
既存記事では PARTITIONED BY (dt, host) の例がありますが、こちらも実環境の partition spec を確認した上で合わせます。
なぜ S3 へ単純コピーしないのか
最初に考えたくなるのは、HDFS 上の Iceberg warehouse をそのまま S3 にコピーする方法です。
aws s3 sync /warehouse/iceberg s3://${ICEBERG_BUCKET}/warehouse
しかし、今回この方式は採用しません。
Iceberg は単なる Parquet ファイル置き場ではありません。テーブルの状態を、次のようなメタデータで管理しています。
- data files
metadata.json- manifest
- manifest list
- snapshot
- schema 情報
- partition spec
- snapshot history
Iceberg の metadata には、どの data file が現在のテーブルとして有効なのかが記録されています。HDFS 上で作成した Iceberg テーブルであれば、metadata 内に次のような HDFS パスが含まれる可能性があります。
hdfs://cluster1/warehouse/iceberg/logs/syslog_iceberg/...
この状態でファイル群だけを S3 にコピーしても、AWS 側の Athena から見たときに metadata が HDFS を参照したままになります。
つまり、S3 に Parquet ファイルが存在していても、Iceberg テーブルとして正しく読めるとは限りません。
そのため今回は、次のように Spark + Iceberg でテーブルとして読み、S3 側の Iceberg テーブルとして書き直します。
HDFS Iceberg
↓
Spark + Iceberg
↓
S3 Iceberg
この形にすると、AWS 側の Iceberg metadata は S3 上の data file を参照する形で作成されます。
採用構成
全体構成は次の通りです。
ポイントは、自宅側と AWS 側で Iceberg metadata を共有しないことです。
自宅側:
hdfs://cluster1/warehouse/iceberg/logs/syslog_iceberg/
hdfs://cluster1/warehouse/iceberg/logs/authlog_iceberg/
AWS側:
s3://${ICEBERG_BUCKET}/warehouse/logs/syslog_iceberg/
s3://${ICEBERG_BUCKET}/warehouse/logs/authlog_iceberg/
同じログデータを扱いますが、テーブルとしては別物です。
Planeで整理する
今回の構成は、Data Plane、Metadata / Catalog Plane、Query Plane に分けて考えると整理しやすいです。
| 観点 | 自宅側 | AWS側 |
|---|---|---|
| Data Plane | HDFS 上の Parquet data files | S3 上の Parquet data files |
| Metadata / Catalog Plane | Hive Metastore + Iceberg metadata | Glue Data Catalog + Iceberg metadata |
| Query Plane | Spark / Trino | Athena |
Iceberg では、実データの Parquet と、テーブル状態を管理する metadata の両方が重要です。
AWS 側では、S3 に Iceberg warehouse を置き、Glue Data Catalog にテーブル情報を登録し、Athena から SQL で参照します。
AWS側コンポーネント
Amazon S3
S3 は AWS 側 Iceberg テーブルの実体を置く場所です。
s3://${ICEBERG_BUCKET}/warehouse/logs/syslog_iceberg/
s3://${ICEBERG_BUCKET}/warehouse/logs/authlog_iceberg/
Iceberg テーブル配下には、主に次のようなディレクトリが作成されます。
data/
metadata/
Athena は Iceberg metadata をたどって、必要な Parquet data file を読みます。
AWS Glue Data Catalog
Glue Data Catalog は、Athena から Iceberg テーブルを見つけるための catalog として使います。
Spark 側では、AWS 側 catalog を概念的に次のように定義します。
catalog name:
glue_prod
catalog implementation:
org.apache.iceberg.aws.glue.GlueCatalog
FileIO:
org.apache.iceberg.aws.s3.S3FileIO
warehouse:
s3://${ICEBERG_BUCKET}/warehouse
Glue Data Catalog には database / table の情報が入り、Iceberg の詳細な snapshot や manifest は S3 側の metadata に保存されます。
Amazon Athena
Athena はサーバーレスに SQL を実行するための query engine として使います。
EC2 を24時間起動して Trino クラスタを運用するのではなく、必要なときだけ Athena でクエリを実行できる構成にします。
IAM
Spark から S3 と Glue に書き込むため、IAM 権限が必要です。
ただし、AdministratorAccess を前提にはしません。少なくとも次の対象に絞った権限を用意します。
- 対象 S3 bucket への read / write
- 対象 Glue database / table への read / write
- Athena query result bucket への read / write
-
sts:GetCallerIdentityによる疎通確認
アクセスキーを記事や GitHub に直接書かず、AWS credential provider chain、環境変数、AWS profile、EC2 を使う場合は IAM Role を使います。
Catalog設計
自宅側の catalog は既存の hive_prod を維持します。
spark.sql.catalog.hive_prod=org.apache.iceberg.spark.SparkCatalog
spark.sql.catalog.hive_prod.type=hive
spark.sql.catalog.hive_prod.uri=thrift://hive1:9083,thrift://hive2:9083
spark.sql.catalog.hive_prod.warehouse=hdfs://cluster1/warehouse/iceberg
AWS 側には新しく glue_prod を追加します。
spark.sql.catalog.glue_prod=org.apache.iceberg.spark.SparkCatalog
spark.sql.catalog.glue_prod.catalog-impl=org.apache.iceberg.aws.glue.GlueCatalog
spark.sql.catalog.glue_prod.io-impl=org.apache.iceberg.aws.s3.S3FileIO
spark.sql.catalog.glue_prod.warehouse=s3://${ICEBERG_BUCKET}/warehouse
spark.sql.catalog.glue_prod.client.region=ap-northeast-1
hive_prod を置き換えるのではなく、併存させます。
hive_prod = 自宅HDFS Iceberg
glue_prod = AWS S3 Iceberg
Spark から両方の catalog を参照できる状態にすることで、次のような SQL で複製できます。
INSERT INTO glue_prod.logs.syslog_iceberg
SELECT *
FROM hive_prod.logs.syslog_iceberg
WHERE dt = DATE '2026-08-18';
テーブル配置設計
自宅側は既存の配置をそのまま使います。
hdfs://cluster1/warehouse/iceberg/logs/syslog_iceberg/
hdfs://cluster1/warehouse/iceberg/logs/authlog_iceberg/
AWS 側は次の配置にします。
s3://${ICEBERG_BUCKET}/warehouse/logs/syslog_iceberg/
s3://${ICEBERG_BUCKET}/warehouse/logs/authlog_iceberg/
テーブル名は同じですが、catalog が異なります。
| 用途 | Catalog | Table |
|---|---|---|
| 自宅側 syslog | hive_prod |
logs.syslog_iceberg |
| 自宅側 authlog | hive_prod |
logs.authlog_iceberg |
| AWS側 syslog | glue_prod |
logs.syslog_iceberg |
| AWS側 authlog | glue_prod |
logs.authlog_iceberg |
AWS 側の Iceberg metadata は S3 上に作成し、自宅側 metadata とは共有しません。
データ転送方式
最初からリアルタイム同期にはしません。PoC では、1日1回程度の日次バッチ複製にします。
例えば前日分だけを抽出します。
WHERE dt = DATE '2026-08-18'
転送方式を比較すると次のようになります。
| 方式 | 概要 | PoCでの扱い |
|---|---|---|
| テーブル全量コピー | 毎回全データをコピー | 単純だがデータ量が増えると重い |
| 日次差分コピー | 対象日だけコピー | 最初の方式として採用 |
Spark SQL INSERT INTO
|
SQL で Iceberg から Iceberg へ挿入 | 採用しやすい |
Spark SQL MERGE INTO
|
キーに基づいて upsert | 重複排除キー設計が必要 |
Spark DataFrame writeTo
|
アプリケーションコードで制御 | 柔軟だが記事では SQL 優先 |
| DELETE + INSERT | 対象日を消してから再投入 | PoC の冪等性確保に採用 |
今回の第一候補は DELETE + INSERT です。
DELETE FROM glue_prod.logs.syslog_iceberg
WHERE dt = DATE '2026-08-18';
INSERT INTO glue_prod.logs.syslog_iceberg
SELECT *
FROM hive_prod.logs.syslog_iceberg
WHERE dt = DATE '2026-08-18';
authlog_iceberg も同じ考え方です。
DELETE FROM glue_prod.logs.authlog_iceberg
WHERE dt = DATE '2026-08-18';
INSERT INTO glue_prod.logs.authlog_iceberg
SELECT *
FROM hive_prod.logs.authlog_iceberg
WHERE dt = DATE '2026-08-18';
MERGE INTO は、ログ1行を一意に識別できるキーを設計できている場合には有効です。一方、今回のようなログデータ複製 PoC では、対象日単位で入れ直すほうが処理内容を確認しやすく、再実行もしやすいです。
冪等性
日次バッチでは、同じ日付を再実行することがあります。
例えば以下のようなケースです。
- 前日の同期が途中で失敗した
- schema や設定を修正して再実行した
- Athena 側の確認で件数不一致が見つかった
このとき、単純な INSERT INTO だけだと同じ日のデータが重複する可能性があります。
そこで PoC では、対象日の AWS 側データを削除してから挿入します。
対象日のAWS側データをDELETE
↓
自宅IcebergからINSERT
↓
件数確認
この方式なら、同じ DT を指定して再実行しても件数が増え続けません。
パーティション設計
ログ分析では、日付で絞り込むクエリが多くなります。
SELECT
host,
program,
count(*) AS cnt
FROM logs.syslog_iceberg
WHERE dt = DATE '2026-08-18'
GROUP BY host, program;
既存記事では Iceberg テーブルの例として PARTITIONED BY (dt, host) を使っています。
PARTITIONED BY (dt, host)
ただし、構築時には必ず実環境で確認します。
DESCRIBE TABLE hive_prod.logs.syslog_iceberg;
DESCRIBE TABLE hive_prod.logs.authlog_iceberg;
SHOW CREATE TABLE hive_prod.logs.syslog_iceberg;
SHOW CREATE TABLE hive_prod.logs.authlog_iceberg;
AWS 側も、Athena で dt による絞り込みが効くように partition spec を合わせます。
Iceberg では Hive 形式の dt=2026-08-18/host=... のようなディレクトリ構造を直接意識してクエリするのではなく、Iceberg metadata を通じて partition pruning が行われます。
そのため、Athena からは普通に WHERE dt = DATE '2026-08-18' と書きます。
Athenaとの相性
Athena はスキャン量に応じて料金が発生します。したがって、次の設計が重要です。
- Parquet で列指向に保存する
- 必要な列だけ
SELECTする -
dtで絞り込む - Iceberg の partition pruning を効かせる
悪い例です。
SELECT *
FROM logs.syslog_iceberg;
改善例です。
SELECT
host,
program,
msg
FROM logs.syslog_iceberg
WHERE dt = DATE '2026-08-18';
authlog_iceberg でも同じです。
SELECT
host,
program,
count(*) AS cnt
FROM logs.authlog_iceberg
WHERE dt = DATE '2026-08-18'
GROUP BY host, program
ORDER BY cnt DESC;
また、Athena で Iceberg を読む場合は対応状況を確認します。記事執筆時点の AWS 公式ドキュメントでは、Athena は Iceberg v2 テーブルを作成・操作し、Glue Catalog に登録された Iceberg テーブルを対象にします。一方で、Athena 側の Iceberg 対応バージョン、timestamp 精度、未対応操作には制約があります。
今回のように Spark + Iceberg 1.10.1 で AWS 側テーブルを書き込む場合も、Athena から読めることを実データで確認します。特に、新しい Iceberg 機能や特殊な table property に依存しすぎないようにします。
コスト設計
AWS 側で主に発生する料金は次の通りです。
- S3 保存料金
- S3 API リクエスト料金
- Athena スキャン料金
- Athena query result 保存用 S3
- Glue Data Catalog のメタデータ料金
- 自宅から AWS へアップロードする通信コスト
特に重要なのは、Athena がスキャン量課金である点です。
例えば小規模 PoC として次の条件を考えます。
Iceberg総容量: 100GB
Athenaクエリ: 月100回
平均スキャン量: 1GB/query
月間スキャン量は約 100GB です。
Athena の料金は公式ページ上で TB 単位のスキャン量課金として案内されています。単価はリージョンや時点で変わる可能性があるため、記事執筆時点の AWS 公式料金ページを確認してください。
月間Athena概算 = 月間スキャン量 × Athenaの1TBあたり料金
この程度の小規模 PoC で、dt による絞り込み、列選択、Parquet、Iceberg partition pruning が効いていれば、Athena 部分は月額数ドル規模に収まる可能性があります。
ただし、S3 保存容量、S3 request、Glue Data Catalog、Athena query result の保存容量も含めて確認します。
最新料金は AWS 公式料金ページを確認してください。
参考:
- Amazon Athena Pricing: https://aws.amazon.com/athena/pricing/
- Amazon S3 Pricing: https://aws.amazon.com/s3/pricing/
- AWS Glue Pricing: https://aws.amazon.com/glue/pricing/
セキュリティ設計
PoC でも、credential の扱いは雑にしないようにします。
最低限、次の方針にします。
- IAM User または IAM Role を用途別に用意する
- 権限は対象 bucket / database / table に絞る
-
AdministratorAccessを前提にしない - Access Key / Secret Access Key を記事に書かない
- GitHub や Qiita に credential を載せない
- S3 Block Public Access を有効にする
- 通信は HTTPS を利用する
- Spark 設定ファイルに Secret を平文直書きしない
- AWS credential provider chain を利用する
- ローカル実行では AWS profile を利用する
- EC2 など AWS 上で実行する場合は IAM Role を優先する
Spark から AWS へアクセスする場合も、次のような値を設定ファイルに直接書くことは避けます。
aws_access_key_id
aws_secret_access_key
代わりに、実行ユーザー spark の profile、環境変数、インスタンスロールなどを使います。
AWSリージョン
本記事では例として東京リージョンを使います。
AWS_REGION=ap-northeast-1
ただし、bucket 名、region、profile はプレースホルダー化しておきます。
ICEBERG_BUCKET="your-iceberg-bucket"
ATHENA_RESULT_BUCKET="your-athena-result-bucket"
AWS_PROFILE="your-aws-profile"
実際には、自宅からの通信経路、利用者の所在地、Athena / Glue / S3 を置きたいリージョン、料金を見て決めます。
障害時の考え方
AWS 側は自宅 Iceberg の分析用コピーです。
AWS側障害
→ 自宅Icebergには影響しない
逆に、自宅側に障害が発生しても、AWS 側に同期済みの範囲は Athena から参照できます。
自宅側障害
→ AWS側に同期済みの範囲はAthenaから参照可能
この意味では、簡易 DR 的な役割もあります。
ただし、AWS 側コピーを完全なバックアップと断定しません。バックアップとして扱うには、少なくとも次の観点を別途設計する必要があります。
- 最終同期時刻
- Iceberg snapshot の保持
- metadata 整合性
- 同期漏れ検知
- S3 versioning
- バックアップ保持期間
- 復旧手順
今回の目的は、あくまで Athena 分析用コピーを作ることです。
最終構成
設計上の最終形は次の通りです。
まとめ
この記事では、自宅 HDFS 上の Apache Iceberg を Source of Truth として残しながら、AWS 側に S3 + Glue Data Catalog + Athena の分析用 Iceberg コピーを作る設計を整理しました。
重要な点は、HDFS 上の Iceberg ディレクトリを S3 へ単純コピーしないことです。Spark + Iceberg を使って、AWS 側に独立した Iceberg テーブルとして書き直します。
次の構築編では、実際に以下を作ります。
- S3 bucket
- Glue database
- Spark の
glue_prodcatalog glue_prod.logs.syslog_icebergglue_prod.logs.authlog_iceberg- 日次同期スクリプト
- Athena からの確認クエリ
続きの記事はこちらです。
- 自宅HDFS上のApache IcebergをAmazon S3へ複製しAthenaから分析してみる【構築編】
- AWS S3上のApache IcebergをAthena向けに運用する【運用編】
- AWS S3上のApache IcebergをAthena経由で可視化する【発展編】
最終的には、次の比較につなげます。
自宅 HDFS + Iceberg + Trino
vs
AWS S3 + Iceberg + Athena
参考
- Apache Iceberg AWS integration: https://iceberg.apache.org/docs/latest/aws/
- Apache Iceberg Spark configuration: https://iceberg.apache.org/docs/latest/spark-configuration/
- Amazon Athena Iceberg tables: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg.html
- Amazon Athena Pricing: https://aws.amazon.com/athena/pricing/
- Amazon S3 Pricing: https://aws.amazon.com/s3/pricing/
- AWS Glue Pricing: https://aws.amazon.com/glue/pricing/