はじめに
前回までの記事で、自宅 HDFS 上の Apache Iceberg を Source of Truth として残し、AWS 側に S3 + Glue Data Catalog + Athena の分析用コピーを作りました。
- 自宅HDFS上のApache IcebergをAWS S3へ複製してAthenaで分析する構成を考える【設計編】
- 自宅HDFS上のApache IcebergをAmazon S3へ複製しAthenaから分析してみる【構築編】
今回は運用編です。
AWS 側 Iceberg コピーを作った後に必要になる、次の運用を整理します。
- snapshot 管理
- orphan file 削除
- compaction
- S3 Versioning
- Athena スキャン量監視
- 運用スクリプト化
今回も基本方針は変えません。
自宅 = メイン環境 / Source of Truth
AWS = Athena分析用コピー
AWS 側は便利な分析環境ですが、自宅 Iceberg の代替ではありません。自宅側を正本として維持し、AWS 側は同期済みデータを Athena から分析する用途にします。
本記事は、構成・実装方法の整理を目的として作成したもので、現時点では記載内容について実環境での一連の動作検証は実施していません。
可能な範囲で公式ドキュメント等を参照して記載していますが、環境や各サービスのバージョンによっては、手順どおりに動作しない可能性があります。
今後、実機で検証する機会があれば、確認できた内容や修正点を記事へ反映する予定です。
運用対象
AWS 側には、次の Iceberg テーブルが作成済みである前提です。
glue_prod.logs.syslog_iceberg
glue_prod.logs.authlog_iceberg
S3 上の配置例です。
s3://your-iceberg-bucket/warehouse/logs/syslog_iceberg/
s3://your-iceberg-bucket/warehouse/logs/authlog_iceberg/
Athena からは以下のように参照します。
SELECT count(*) FROM logs.syslog_iceberg;
SELECT count(*) FROM logs.authlog_iceberg;
なぜ運用が必要か
Iceberg は snapshot を持つテーブル形式です。
INSERT、DELETE、OPTIMIZE などを実行すると、テーブルの状態は新しい snapshot として管理されます。
これは非常に便利ですが、運用を続けると以下が増えます。
- 古い snapshot
- 古い metadata file
- 現在の snapshot から参照されない data file
- orphan file
- 小さい Parquet file
- Athena query result
- S3 の noncurrent version
放置すると、S3 保存容量、S3 request、Athena スキャン量、metadata 読み込みが増えます。
特に今回のように日次同期で DELETE + INSERT を繰り返す構成では、AWS 側にも Iceberg の保守が必要です。
運用方針
PoC では、以下の方針にします。
| 項目 | 方針 |
|---|---|
| 書き込み担当 | Spark |
| 参照担当 | Athena |
| 日次同期 | 自宅 Iceberg から AWS Iceberg へ DELETE + INSERT
|
| snapshot 保持 | 数日から数週間程度を目安に開始 |
| compaction | 週次または必要時 |
| orphan file 削除 |
VACUUM または Spark 手続きで実施 |
| S3 Versioning | 有効化する場合は lifecycle とセット |
| コスト確認 | Athena Data scanned と S3 容量を定期確認 |
運用初期は、攻めた削除設定にしない方が安全です。
まずは以下を確認します。
- 同期が安定しているか
- Athena から読めるか
- timestamp 表示にずれがないか
- 同じ
DTの再実行で重複しないか - S3 容量がどれくらい増えるか
- Athena の Data scanned がどれくらいか
変数
以降のコマンドでは以下を使います。
実行ユーザー: 通常ユーザーまたは spark
export AWS_REGION=ap-northeast-1
export AWS_PROFILE="your-aws-profile"
export ICEBERG_BUCKET="your-iceberg-bucket"
export ATHENA_RESULT_BUCKET="your-athena-result-bucket"
export ATHENA_WORKGROUP="primary"
export GLUE_DATABASE="logs"
環境に合わせて置き換えてください。
現状確認
まず、AWS 側 Iceberg テーブルが見えることを確認します。
実行ユーザー: spark
aws glue get-table \
--region "${AWS_REGION}" \
--database-name "${GLUE_DATABASE}" \
--name syslog_iceberg \
--profile "${AWS_PROFILE}"
aws glue get-table \
--region "${AWS_REGION}" \
--database-name "${GLUE_DATABASE}" \
--name authlog_iceberg \
--profile "${AWS_PROFILE}"
S3 側も確認します。
aws s3 ls \
s3://${ICEBERG_BUCKET}/warehouse/logs/syslog_iceberg/ \
--recursive \
--summarize \
--profile "${AWS_PROFILE}"
aws s3 ls \
s3://${ICEBERG_BUCKET}/warehouse/logs/authlog_iceberg/ \
--recursive \
--summarize \
--profile "${AWS_PROFILE}"
Iceberg テーブル配下には、少なくとも以下が存在します。
data/
metadata/
Athenaからmetadataを確認する
Athena では Iceberg の metadata table を参照できます。
まず snapshot を確認します。
SELECT *
FROM "logs"."syslog_iceberg$snapshots"
ORDER BY committed_at DESC
LIMIT 20;
SELECT *
FROM "logs"."authlog_iceberg$snapshots"
ORDER BY committed_at DESC
LIMIT 20;
現在の data file を確認します。
SELECT
file_path,
record_count,
file_size_in_bytes
FROM "logs"."syslog_iceberg$files"
ORDER BY file_size_in_bytes ASC
LIMIT 50;
SELECT
file_path,
record_count,
file_size_in_bytes
FROM "logs"."authlog_iceberg$files"
ORDER BY file_size_in_bytes ASC
LIMIT 50;
小さい file が多い場合、Athena での読み込み効率が落ちる可能性があります。
Athenaで実行する運用SQL
Athena 側では、Iceberg table maintenance として主に以下を使います。
| 用途 | Athena SQL |
|---|---|
| data file の再配置 / 小ファイル整理 | OPTIMIZE ... REWRITE DATA USING BIN_PACK |
| snapshot 期限切れ / orphan file 削除 | VACUUM |
AWS 公式ドキュメント上、VACUUM は Athena engine version 3 の Iceberg table でサポートされます。
また、OPTIMIZE の WHERE 句では partition column だけが利用できます。今回の例では dt や host が partition column である前提ですが、実環境では必ず partition spec を確認します。
snapshot保持設定
Athena の VACUUM は table property を見て snapshot を期限切れにします。
まずは保守的に設定します。
例:
最低3世代のsnapshotを残す
7日より古いsnapshotを削除対象にする
metadata fileは30個程度残す
Athena で実行します。
ALTER TABLE logs.syslog_iceberg SET TBLPROPERTIES (
'vacuum_min_snapshots_to_keep'='3',
'vacuum_max_snapshot_age_seconds'='604800',
'vacuum_max_metadata_files_to_keep'='30'
);
ALTER TABLE logs.authlog_iceberg SET TBLPROPERTIES (
'vacuum_min_snapshots_to_keep'='3',
'vacuum_max_snapshot_age_seconds'='604800',
'vacuum_max_metadata_files_to_keep'='30'
);
604800 秒は 7 日です。
注意点です。
- snapshot を削除すると、削除された snapshot への time travel はできなくなる
- 初期運用では短くしすぎない
- 同期失敗時の再確認期間より短くしない
- S3 Versioning を有効化している場合、noncurrent version の容量も別途考慮する
VACUUMでsnapshot期限切れとorphan file削除
VACUUM は snapshot expiration と orphan file removal を行います。
Athena で実行します。
VACUUM logs.syslog_iceberg;
VACUUM logs.authlog_iceberg;
実行前後で snapshot を確認します。
SELECT
committed_at,
snapshot_id,
operation
FROM "logs"."syslog_iceberg$snapshots"
ORDER BY committed_at DESC;
SELECT
committed_at,
snapshot_id,
operation
FROM "logs"."authlog_iceberg$snapshots"
ORDER BY committed_at DESC;
重要な注意点です。
-
VACUUMには S3 delete 権限が必要 - S3 API request 料金が発生する
- Iceberg data が bucket root ではなく folder 配下にある必要がある
- snapshot を削除すると古い snapshot への time travel はできない
構築編では以下のように table 配置しているため、bucket root ではなく folder 配下にあります。
s3://your-iceberg-bucket/warehouse/logs/syslog_iceberg/
s3://your-iceberg-bucket/warehouse/logs/authlog_iceberg/
compaction
小さい Parquet file が増えた場合は、OPTIMIZE を使って data file を再配置します。
Athena で実行します。
OPTIMIZE logs.syslog_iceberg
REWRITE DATA USING BIN_PACK;
OPTIMIZE logs.authlog_iceberg
REWRITE DATA USING BIN_PACK;
ただし、全体に対して実行すると対象 data file が多くなり、スキャン量と実行時間が大きくなる可能性があります。
日次同期構成では、まず対象日や対象 host を絞って実行するのが安全です。
OPTIMIZE logs.syslog_iceberg
REWRITE DATA USING BIN_PACK
WHERE dt = DATE '2026-08-18';
OPTIMIZE logs.authlog_iceberg
REWRITE DATA USING BIN_PACK
WHERE dt = DATE '2026-08-18';
Athena の OPTIMIZE では、WHERE 句に指定できるのは partition column です。非 partition column を指定すると失敗します。
既存の partition spec は Spark 側で確認します。
実行ユーザー: spark
sudo -u spark /usr/local/bin/spark-sql-iceberg-aws <<'EOF'
SHOW CREATE TABLE glue_prod.logs.syslog_iceberg;
SHOW CREATE TABLE glue_prod.logs.authlog_iceberg;
EOF
compaction前後の確認
Athena で file 数と file size を確認します。
SELECT
count(*) AS file_count,
sum(record_count) AS record_count,
round(sum(file_size_in_bytes) / 1024.0 / 1024.0, 2) AS total_mb,
round(avg(file_size_in_bytes) / 1024.0 / 1024.0, 2) AS avg_file_mb
FROM "logs"."syslog_iceberg$files";
SELECT
count(*) AS file_count,
sum(record_count) AS record_count,
round(sum(file_size_in_bytes) / 1024.0 / 1024.0, 2) AS total_mb,
round(avg(file_size_in_bytes) / 1024.0 / 1024.0, 2) AS avg_file_mb
FROM "logs"."authlog_iceberg$files";
OPTIMIZE 後は新しい snapshot が作られます。
そのため、すぐに S3 使用量が減るとは限りません。
OPTIMIZE
↓
新しいdata fileが作成される
↓
古いdata fileは古いsnapshotから参照される
↓
VACUUMで古いsnapshotと不要fileを削除
基本順序は以下です。
OPTIMIZE
↓
Athenaで検索確認
↓
VACUUM
↓
S3容量確認
Spark手続きでmaintenanceする場合
Athena ではなく Spark + Iceberg から maintenance する方法もあります。
AWS 側 catalog が glue_prod として設定されている前提です。
expire_snapshots
実行ユーザー: spark
sudo -u spark \
AWS_PROFILE="${AWS_PROFILE}" \
AWS_REGION="${AWS_REGION}" \
/usr/local/bin/spark-sql-iceberg-aws <<'EOF'
CALL glue_prod.system.expire_snapshots(
table => 'logs.syslog_iceberg',
older_than => TIMESTAMP '2026-08-12 00:00:00',
retain_last => 3
);
CALL glue_prod.system.expire_snapshots(
table => 'logs.authlog_iceberg',
older_than => TIMESTAMP '2026-08-12 00:00:00',
retain_last => 3
);
EOF
remove_orphan_files
まず dry run します。
実行ユーザー: spark
sudo -u spark \
AWS_PROFILE="${AWS_PROFILE}" \
AWS_REGION="${AWS_REGION}" \
/usr/local/bin/spark-sql-iceberg-aws <<'EOF'
CALL glue_prod.system.remove_orphan_files(
table => 'logs.syslog_iceberg',
older_than => TIMESTAMP '2026-08-12 00:00:00',
dry_run => true
);
CALL glue_prod.system.remove_orphan_files(
table => 'logs.authlog_iceberg',
older_than => TIMESTAMP '2026-08-12 00:00:00',
dry_run => true
);
EOF
問題なければ dry_run => false で実行します。
sudo -u spark \
AWS_PROFILE="${AWS_PROFILE}" \
AWS_REGION="${AWS_REGION}" \
/usr/local/bin/spark-sql-iceberg-aws <<'EOF'
CALL glue_prod.system.remove_orphan_files(
table => 'logs.syslog_iceberg',
older_than => TIMESTAMP '2026-08-12 00:00:00',
dry_run => false
);
CALL glue_prod.system.remove_orphan_files(
table => 'logs.authlog_iceberg',
older_than => TIMESTAMP '2026-08-12 00:00:00',
dry_run => false
);
EOF
rewrite_data_files
Spark 側で compaction する場合です。
sudo -u spark \
AWS_PROFILE="${AWS_PROFILE}" \
AWS_REGION="${AWS_REGION}" \
/usr/local/bin/spark-sql-iceberg-aws <<'EOF'
CALL glue_prod.system.rewrite_data_files(
table => 'logs.syslog_iceberg',
where => 'dt = DATE ''2026-08-18'''
);
CALL glue_prod.system.rewrite_data_files(
table => 'logs.authlog_iceberg',
where => 'dt = DATE ''2026-08-18'''
);
EOF
PoC では Athena の OPTIMIZE / VACUUM で始めると分かりやすいです。細かく制御したい場合や既存 Spark 運用に寄せたい場合は Spark 手続きを使います。
maintenanceの実行タイミング
日次同期と maintenance は同時に動かさないようにします。
例です。
01:30 自宅側ログ取り込み完了
02:00 自宅 Iceberg への日次投入
03:00 AWS 側 Iceberg への日次同期
04:00 Athena 件数確認
週1回 04:30 OPTIMIZE
週1回 05:30 VACUUM
同時実行を避けたいものです。
- 日次同期中の
DELETE + INSERT - Athena
OPTIMIZE - Athena
VACUUM - Spark
expire_snapshots - Spark
remove_orphan_files - Spark
rewrite_data_files
特に VACUUM や orphan file 削除は、書き込み処理が完全に終わってから実行します。
Athenaスキャン量監視
Athena は query 実行後に Data scanned が表示されます。
CLI で見る場合は、get-query-execution の Statistics.DataScannedInBytes を確認します。
以下はクエリを実行し、スキャン量と実行時間を表示する簡易スクリプトです。
ファイル:
/opt/iceberg/bin/run_athena_query_with_stats.sh
実行ユーザー: root
sudo tee /opt/iceberg/bin/run_athena_query_with_stats.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
AWS_REGION=${AWS_REGION:-ap-northeast-1}
AWS_PROFILE=${AWS_PROFILE:-default}
ATHENA_WORKGROUP=${ATHENA_WORKGROUP:-primary}
ATHENA_RESULT_BUCKET=${ATHENA_RESULT_BUCKET:?ATHENA_RESULT_BUCKET is required}
SQL=${SQL:?SQL is required}
RESULT_LOCATION="s3://${ATHENA_RESULT_BUCKET}/athena-results/"
log() {
echo "[INFO] $(date '+%F %T') $*"
}
QUERY_ID=$(
aws athena start-query-execution \
--region "${AWS_REGION}" \
--profile "${AWS_PROFILE}" \
--work-group "${ATHENA_WORKGROUP}" \
--query-string "${SQL}" \
--result-configuration "OutputLocation=${RESULT_LOCATION}" \
--query 'QueryExecutionId' \
--output text
)
log "query id=${QUERY_ID}"
while true; do
STATE=$(
aws athena get-query-execution \
--region "${AWS_REGION}" \
--profile "${AWS_PROFILE}" \
--query-execution-id "${QUERY_ID}" \
--query 'QueryExecution.Status.State' \
--output text
)
case "${STATE}" in
SUCCEEDED|FAILED|CANCELLED)
break
;;
*)
sleep 2
;;
esac
done
aws athena get-query-execution \
--region "${AWS_REGION}" \
--profile "${AWS_PROFILE}" \
--query-execution-id "${QUERY_ID}" \
--query 'QueryExecution.{State:Status.State,DataScannedInBytes:Statistics.DataScannedInBytes,EngineMs:Statistics.EngineExecutionTimeInMillis,TotalMs:Statistics.TotalExecutionTimeInMillis}' \
--output table
if [ "${STATE}" != "SUCCEEDED" ]; then
exit 1
fi
EOF
sudo chmod 755 /opt/iceberg/bin/run_athena_query_with_stats.sh
実行例です。
sudo -u spark \
AWS_REGION="${AWS_REGION}" \
AWS_PROFILE="${AWS_PROFILE}" \
ATHENA_WORKGROUP="${ATHENA_WORKGROUP}" \
ATHENA_RESULT_BUCKET="${ATHENA_RESULT_BUCKET}" \
SQL="SELECT host FROM logs.syslog_iceberg WHERE dt = DATE '2026-08-18'" \
/opt/iceberg/bin/run_athena_query_with_stats.sh
スキャン量の見方です。
DataScannedInBytes
÷ 1024 ÷ 1024 = MiB
÷ 1024 ÷ 1024 ÷ 1024 = GiB
÷ 1024 ÷ 1024 ÷ 1024 ÷ 1024 = TiB
Athena の概算料金は以下で考えます。
Athenaクエリ料金 = スキャン量(TB) × Athenaの1TBあたり料金
単価はリージョンや時点で変わる可能性があるため、必ず AWS 公式料金ページを確認します。
スキャン量を減らす運用
Athena のスキャン量を減らすため、以下を徹底します。
-
SELECT *を避ける - 必要な列だけ選択する
-
dtで絞り込む - 必要に応じて
hostも絞る - Parquet を使う
- 小さい file が増えたら compaction する
- 日付範囲を広げすぎない
悪い例です。
SELECT *
FROM logs.syslog_iceberg;
改善例です。
SELECT
host,
program,
count(*) AS cnt
FROM logs.syslog_iceberg
WHERE dt = DATE '2026-08-18'
GROUP BY host, program
ORDER BY cnt DESC;
authlog でも同じです。
SELECT
host,
program,
count(*) AS cnt
FROM logs.authlog_iceberg
WHERE dt = DATE '2026-08-18'
GROUP BY host, program
ORDER BY cnt DESC;
S3容量監視
S3 の容量は AWS CLI で確認できます。
aws s3 ls \
s3://${ICEBERG_BUCKET}/warehouse/ \
--recursive \
--summarize \
--profile "${AWS_PROFILE}"
テーブル別に確認します。
aws s3 ls \
s3://${ICEBERG_BUCKET}/warehouse/logs/syslog_iceberg/ \
--recursive \
--summarize \
--profile "${AWS_PROFILE}"
aws s3 ls \
s3://${ICEBERG_BUCKET}/warehouse/logs/authlog_iceberg/ \
--recursive \
--summarize \
--profile "${AWS_PROFILE}"
Athena query result も溜まります。
aws s3 ls \
s3://${ATHENA_RESULT_BUCKET}/athena-results/ \
--recursive \
--summarize \
--profile "${AWS_PROFILE}"
S3 Versioning
S3 Versioning を有効化すると、削除や上書きに対する復旧余地ができます。
実行ユーザー: 通常ユーザー
aws s3api put-bucket-versioning \
--bucket "${ICEBERG_BUCKET}" \
--versioning-configuration Status=Enabled \
--profile "${AWS_PROFILE}"
確認します。
aws s3api get-bucket-versioning \
--bucket "${ICEBERG_BUCKET}" \
--profile "${AWS_PROFILE}"
ただし、S3 Versioning は無料の保険ではありません。
AWS 公式ドキュメントでは、各 object version は差分ではなく object 全体として保存されるため、version 数に応じて保存料金が増えます。
Iceberg は data file / metadata file を追加しながら管理するため、Versioning を有効化する場合は lifecycle とセットで考えます。
S3 Lifecycle
Versioning を有効化する場合、noncurrent version を放置しないように lifecycle を設定します。
例として、noncurrent version を 30 日で削除します。
ファイル:
s3-lifecycle-iceberg.json
実行ユーザー: 通常ユーザー
cat > s3-lifecycle-iceberg.json <<'EOF'
{
"Rules": [
{
"ID": "expire-noncurrent-iceberg-versions",
"Status": "Enabled",
"Filter": {
"Prefix": "warehouse/"
},
"NoncurrentVersionExpiration": {
"NoncurrentDays": 30
}
},
{
"ID": "expire-athena-query-results",
"Status": "Enabled",
"Filter": {
"Prefix": "athena-results/"
},
"Expiration": {
"Days": 30
}
}
]
}
EOF
Iceberg bucket に設定します。
aws s3api put-bucket-lifecycle-configuration \
--bucket "${ICEBERG_BUCKET}" \
--lifecycle-configuration file://s3-lifecycle-iceberg.json \
--profile "${AWS_PROFILE}"
Athena result bucket が別の場合は、Athena result 用 lifecycle を別に設定します。
cat > s3-lifecycle-athena-results.json <<'EOF'
{
"Rules": [
{
"ID": "expire-athena-query-results",
"Status": "Enabled",
"Filter": {
"Prefix": "athena-results/"
},
"Expiration": {
"Days": 30
}
}
]
}
EOF
aws s3api put-bucket-lifecycle-configuration \
--bucket "${ATHENA_RESULT_BUCKET}" \
--lifecycle-configuration file://s3-lifecycle-athena-results.json \
--profile "${AWS_PROFILE}"
確認します。
aws s3api get-bucket-lifecycle-configuration \
--bucket "${ICEBERG_BUCKET}" \
--profile "${AWS_PROFILE}"
注意点です。
- lifecycle は削除系設定なので、まず短すぎない日数で始める
- Iceberg の snapshot 保持期間より短くしない
- Versioning を有効化した bucket では delete marker と noncurrent version を理解しておく
- 検証環境と本番相当環境で保持期間を分ける
日次確認スクリプト
AWS 側 Iceberg の日次確認用スクリプトを作ります。
ファイル:
/opt/iceberg/bin/check_aws_iceberg_daily.sh
実行ユーザー: root
sudo tee /opt/iceberg/bin/check_aws_iceberg_daily.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
AWS_REGION=${AWS_REGION:-ap-northeast-1}
AWS_PROFILE=${AWS_PROFILE:-default}
ICEBERG_BUCKET=${ICEBERG_BUCKET:?ICEBERG_BUCKET is required}
ATHENA_RESULT_BUCKET=${ATHENA_RESULT_BUCKET:?ATHENA_RESULT_BUCKET is required}
ATHENA_WORKGROUP=${ATHENA_WORKGROUP:-primary}
DT=${DT:-$(date -d yesterday +%F)}
RUN_ATHENA=${RUN_ATHENA:-/opt/iceberg/bin/run_athena_query_with_stats.sh}
log() {
echo "[INFO] $(date '+%F %T') $*"
}
run_athena() {
local sql="$1"
AWS_REGION="${AWS_REGION}" \
AWS_PROFILE="${AWS_PROFILE}" \
ATHENA_RESULT_BUCKET="${ATHENA_RESULT_BUCKET}" \
ATHENA_WORKGROUP="${ATHENA_WORKGROUP}" \
SQL="${sql}" \
"${RUN_ATHENA}"
}
log "check aws iceberg daily: DT=${DT}"
log "S3 summarize: syslog"
aws s3 ls \
s3://${ICEBERG_BUCKET}/warehouse/logs/syslog_iceberg/ \
--recursive \
--summarize \
--profile "${AWS_PROFILE}" \
| tail -5
log "S3 summarize: authlog"
aws s3 ls \
s3://${ICEBERG_BUCKET}/warehouse/logs/authlog_iceberg/ \
--recursive \
--summarize \
--profile "${AWS_PROFILE}" \
| tail -5
log "Athena count: syslog"
run_athena "SELECT count(*) FROM logs.syslog_iceberg WHERE dt = DATE '${DT}'"
log "Athena count: authlog"
run_athena "SELECT count(*) FROM logs.authlog_iceberg WHERE dt = DATE '${DT}'"
log "done"
EOF
sudo chmod 755 /opt/iceberg/bin/check_aws_iceberg_daily.sh
実行例です。
sudo -u spark \
AWS_REGION="${AWS_REGION}" \
AWS_PROFILE="${AWS_PROFILE}" \
ICEBERG_BUCKET="${ICEBERG_BUCKET}" \
ATHENA_RESULT_BUCKET="${ATHENA_RESULT_BUCKET}" \
ATHENA_WORKGROUP="${ATHENA_WORKGROUP}" \
DT=2026-08-18 \
/opt/iceberg/bin/check_aws_iceberg_daily.sh
週次maintenanceスクリプト
週次で OPTIMIZE と VACUUM を実行する例です。
対象日のみ compaction したい場合は DT を指定します。
ファイル:
/opt/iceberg/bin/maintain_aws_iceberg_weekly.sh
実行ユーザー: root
sudo tee /opt/iceberg/bin/maintain_aws_iceberg_weekly.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
AWS_REGION=${AWS_REGION:-ap-northeast-1}
AWS_PROFILE=${AWS_PROFILE:-default}
ATHENA_RESULT_BUCKET=${ATHENA_RESULT_BUCKET:?ATHENA_RESULT_BUCKET is required}
ATHENA_WORKGROUP=${ATHENA_WORKGROUP:-primary}
DT=${DT:-}
RUN_ATHENA=${RUN_ATHENA:-/opt/iceberg/bin/run_athena_query_with_stats.sh}
TABLES=(
syslog_iceberg
authlog_iceberg
)
log() {
echo "[INFO] $(date '+%F %T') $*"
}
run_athena() {
local sql="$1"
AWS_REGION="${AWS_REGION}" \
AWS_PROFILE="${AWS_PROFILE}" \
ATHENA_RESULT_BUCKET="${ATHENA_RESULT_BUCKET}" \
ATHENA_WORKGROUP="${ATHENA_WORKGROUP}" \
SQL="${sql}" \
"${RUN_ATHENA}"
}
for table in "${TABLES[@]}"; do
log "set vacuum properties: ${table}"
run_athena "ALTER TABLE logs.${table} SET TBLPROPERTIES ('vacuum_min_snapshots_to_keep'='3','vacuum_max_snapshot_age_seconds'='604800','vacuum_max_metadata_files_to_keep'='30')"
if [ -n "${DT}" ]; then
log "optimize ${table}: dt=${DT}"
run_athena "OPTIMIZE logs.${table} REWRITE DATA USING BIN_PACK WHERE dt = DATE '${DT}'"
else
log "optimize ${table}: full table"
run_athena "OPTIMIZE logs.${table} REWRITE DATA USING BIN_PACK"
fi
log "vacuum ${table}"
run_athena "VACUUM logs.${table}"
done
log "weekly maintenance completed"
EOF
sudo chmod 755 /opt/iceberg/bin/maintain_aws_iceberg_weekly.sh
実行例です。
sudo -u spark \
AWS_REGION="${AWS_REGION}" \
AWS_PROFILE="${AWS_PROFILE}" \
ATHENA_RESULT_BUCKET="${ATHENA_RESULT_BUCKET}" \
ATHENA_WORKGROUP="${ATHENA_WORKGROUP}" \
DT=2026-08-18 \
/opt/iceberg/bin/maintain_aws_iceberg_weekly.sh
全 table 対象にする場合です。
sudo -u spark \
AWS_REGION="${AWS_REGION}" \
AWS_PROFILE="${AWS_PROFILE}" \
ATHENA_RESULT_BUCKET="${ATHENA_RESULT_BUCKET}" \
ATHENA_WORKGROUP="${ATHENA_WORKGROUP}" \
/opt/iceberg/bin/maintain_aws_iceberg_weekly.sh
小規模 PoC では全 table 対象でも問題ない場合がありますが、データ量が増えたら DT 指定で分割します。
systemd timer化
週次で実行する例です。
実行ユーザー: root
sudo tee /etc/systemd/system/maintain-aws-iceberg.service > /dev/null <<EOF
[Unit]
Description=Maintain AWS Iceberg tables
[Service]
Type=oneshot
User=spark
Group=spark
Environment=AWS_REGION=${AWS_REGION}
Environment=AWS_PROFILE=${AWS_PROFILE}
Environment=ATHENA_RESULT_BUCKET=${ATHENA_RESULT_BUCKET}
Environment=ATHENA_WORKGROUP=${ATHENA_WORKGROUP}
ExecStart=/opt/iceberg/bin/maintain_aws_iceberg_weekly.sh
EOF
sudo tee /etc/systemd/system/maintain-aws-iceberg.timer > /dev/null <<'EOF'
[Unit]
Description=Weekly maintain AWS Iceberg tables
[Timer]
OnCalendar=Sun *-*-* 05:30:00
Persistent=true
[Install]
WantedBy=timers.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now maintain-aws-iceberg.timer
確認します。
systemctl list-timers maintain-aws-iceberg.timer
sudo systemctl status maintain-aws-iceberg.timer --no-pager
手動実行する場合です。
sudo systemctl start maintain-aws-iceberg.service
journalctl -u maintain-aws-iceberg.service -n 100 --no-pager
IAM権限の追加確認
運用では、構築時より削除系権限が重要になります。
最低限、次の権限が必要です。
athena:StartQueryExecutionathena:GetQueryExecutionathena:GetQueryResultsglue:GetDatabaseglue:GetTableglue:UpdateTables3:GetObjects3:PutObjects3:DeleteObjects3:ListBuckets3:GetBucketLocation
特に VACUUM で不要 file を削除したい場合、Iceberg table の S3 bucket に対する s3:DeleteObject が必要です。
S3 Versioning や lifecycle を設定する運用者には、さらに以下が必要になります。
s3:PutBucketVersionings3:GetBucketVersionings3:PutLifecycleConfigurations3:GetLifecycleConfiguration
権限は対象 bucket / database / table に絞ります。
障害時の考え方
OPTIMIZE失敗
OPTIMIZE が失敗した場合は、まず Athena の query error を確認します。
-
WHEREに非 partition column を指定していないか - Athena engine version が対応しているか
- S3 / Glue 権限が不足していないか
- 対象 table が Glue Catalog の Iceberg table として見えているか
失敗した場合でも、自宅側 Iceberg には影響しません。
VACUUM失敗
VACUUM が失敗した場合は、削除権限や table location を確認します。
-
s3:DeleteObjectがあるか - Iceberg table が bucket root ではなく folder 配下にあるか
- maintenance 中に同期 job が動いていないか
Athenaスキャン量が急増
以下を確認します。
-
WHERE dt = ...を付け忘れていないか -
SELECT *を実行していないか - 直近の同期で小さい file が大量に作られていないか
- partition pruning が効く条件になっているか
- 対象日数が広すぎないか
運用チェックリスト
日次:
- AWS 側同期が成功している
- syslog / authlog の件数が自宅側と一致している
- Athena で代表クエリが実行できる
- Data scanned が極端に増えていない
週次:
- snapshot 数を確認する
- small file 数を確認する
- 必要に応じて
OPTIMIZEを実行する -
VACUUMを実行する - S3 容量を確認する
月次:
- S3 Versioning / lifecycle の効き方を確認する
- Athena query result の残りを確認する
- IAM Access Key の棚卸しをする
- AWS 料金を確認する
- snapshot 保持期間が運用に合っているか見直す
まとめ
AWS 側 Iceberg コピーは、作って終わりではありません。
日次同期で DELETE + INSERT を繰り返すと、snapshot、metadata、data file、orphan file、小さい file が増えます。
今回の運用方針は次の通りです。
- Athena の
OPTIMIZEで小さい file を整理する - Athena の
VACUUMで snapshot 期限切れと orphan file 削除を行う - Spark 手続きも代替手段として使えるようにしておく
- S3 Versioning は lifecycle とセットで使う
- Athena Data scanned を定期的に確認する
- 自宅 Iceberg を Source of Truth として残す
これで、AWS 側の S3 / Glue / Athena を分析用コピーとして継続運用しやすくなります。
Athena で分析できるログを Dashboard として継続的に確認する場合は、QuickSight / Amazon Quick や Amazon Managed Grafana を追加する構成もあります。
参考
- Amazon Athena Iceberg tables: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg.html
- Amazon Athena OPTIMIZE: https://docs.aws.amazon.com/athena/latest/ug/optimize-statement.html
- Amazon Athena VACUUM: https://docs.aws.amazon.com/athena/latest/ug/vacuum-statement.html
- Amazon Athena Optimize Iceberg tables: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-data-optimization.html
- Apache Iceberg Spark procedures: https://iceberg.apache.org/docs/latest/spark-procedures/
- Amazon S3 Versioning: https://docs.aws.amazon.com/AmazonS3/latest/userguide/Versioning.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/