0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS S3上のApache IcebergをAthena向けに運用する【運用編】

0
Last updated at Posted at 2026-08-19

はじめに

前回までの記事で、自宅 HDFS 上の Apache Iceberg を Source of Truth として残し、AWS 側に S3 + Glue Data Catalog + 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 を持つテーブル形式です。

INSERTDELETEOPTIMIZE などを実行すると、テーブルの状態は新しい 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 でサポートされます。

また、OPTIMIZEWHERE 句では partition column だけが利用できます。今回の例では dthost が 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-executionStatistics.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スクリプト

週次で OPTIMIZEVACUUM を実行する例です。

対象日のみ 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:StartQueryExecution
  • athena:GetQueryExecution
  • athena:GetQueryResults
  • glue:GetDatabase
  • glue:GetTable
  • glue:UpdateTable
  • s3:GetObject
  • s3:PutObject
  • s3:DeleteObject
  • s3:ListBucket
  • s3:GetBucketLocation

特に VACUUM で不要 file を削除したい場合、Iceberg table の S3 bucket に対する s3:DeleteObject が必要です。

S3 Versioning や lifecycle を設定する運用者には、さらに以下が必要になります。

  • s3:PutBucketVersioning
  • s3:GetBucketVersioning
  • s3:PutLifecycleConfiguration
  • s3: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 を追加する構成もあります。


参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?