tidb-proxy ログの Iceberg テーブルを S3 Tables へ移行する
起票日: 2026-10-06
対象環境: dev / prd 共用ログ基盤 (
st-tidb-proxy-logs)ステータス: 2026-10-06 に dev / prd 共用環境へ適用済み。旧 Glue テーブルと
iceberg/prefix は 2026-10-07 に撤去済み関連タスク:
関連実装:
iac/aws/lib/analytics/tidb-proxy-log-analytics-construct.ts
背景
通常 S3 + Glue Data Catalog の Iceberg テーブルでは、snapshot expiration / orphan file removal を自前で回す必要がある。2026-08 に metadata が約 1.5 TiB まで膨らみ、日次 Athena VACUUM(Step Functions + EventBridge)と保持期間 SQL を運用で抱えることになった。
2026-07-10 の設計時は「S3 Tables は Lake Formation 統合の設定が増える」ため見送ったが、その後 S3 Tables と AWS 分析サービスの統合は IAM アクセス制御がデフォルト になり、Lake Formation は任意になった(2026-03-17 の What's New、S3 User Guide「Integrating Amazon S3 Tables with AWS analytics services」)。前提が変わったため、メンテナンスを S3 Tables のマネージド機能に寄せる。
意思決定
決定事項
# |
決定 |
一言理由 |
|---|---|---|
D1 |
Iceberg テーブルを通常 S3 + Glue から S3 Tables に移す |
テーブルメンテナンスの自前運用をやめる |
D2 |
統合は IAM アクセス制御モード。Lake Formation は使わない |
2026-07-10 に見送った理由(LF の設定コスト)が解消されている |
D3 |
VACUUM の自前運用(Step Functions / EventBridge / スクリプト / SQL)を撤去 |
S3 Tables のマネージドメンテナンスと二重になる |
D4 |
既存データは移行しない。旧テーブルは確認後に撤去(2026-10-07 実施) |
過去のアクセスログを遡って調べる需要が低い |
D5 |
Firehose は新 stream |
宛先 catalog の変更は stream の replacement になり、固定名のままでは更新できない |
検討した選択肢 (D1 / D2)
案 |
メンテナンス |
構成の複雑さ |
判断 |
|---|---|---|---|
A. 現状維持(通常 S3 + Glue + 日次VACUUM) |
自前。VACUUM 停止で metadata が二次関数的に増える |
Step Functions / EventBridge / IAM 調整を抱え続ける |
不採用。2026-08 の高額化の再発リスクが残る |
B. S3 Tables + IAM アクセス制御 |
compaction / snapshot 期限切れ / 未参照ファイル削除を AWS が実施 |
|
採用 |
C. S3 Tables + Lake Formation |
B と同じ |
data location 登録・LF grant・LF 管理者設定が増える |
不採用。細かい権限制御が不要な単一運用者の構成 |
D. Iceberg をやめて gzip JSONL にする |
不要(テーブルの状態を持たない)。保持期間は S3 Lifecycle で切れる |
Firehose 宛先・Glue テーブル(partition projection)・Athena クエリを作り直す |
不採用。後述 |
判断の根拠
2026-07-10 の不採用理由は「Lake Formation 統合の設定が増える」だった。現在の AWS ドキュメントでは統合は IAM 権限がデフォルトで、Lake Formation は任意(S3 User Guide「Integrating Amazon S3 Tables with AWS analytics services」、Firehose Developer Guide「Grant Firehose access to Amazon S3 Tables」の IAM access control)
2026-08-23 の高額化は Iceberg の snapshot / metadata を expire しなかったことが原因で、データ量ではなく メンテナンスの欠如 が問題だった。メンテナンスをマネージドに寄せるのが根本対策になる
案 D について。append only でデータ量が少なく、Athena からしか読まないログなので、Iceberg の機能(ACID・行単位の更新削除・ファイル統計による読み飛ばし・time travel)はほぼ効かず、gzip JSONL でも要件は満たせる。全件スキャンしても Athena は 1 クエリ $0.002 未満。ただし S3 Tables なら FireLens → Firehose (Iceberg 宛先) → Athena の流れをそのまま残し、テーブルの置き場所を替えるだけで済む。JSONL は宛先とクエリ層の作り直しになるため、変更量が小さい案 B を採る
データ量(数百 MiB 規模)では、S3 Tables のストレージ単価の上乗せ(S3 Standard 比 約 15%)や監視・compaction 料金は月数セントの見込みで、自前運用のコストより小さい
受け入れるトレードオフ
s3tablescatalogはアカウント・リージョンで 1 つの federated catalog で、それを本スタックが所有する。本スタックを destroy すると他の table bucket の分析サービス統合にも影響する(現時点で他の table bucket は無い)Firehose ロールの Glue 権限は、federated catalog 配下の ARN 形式を確認しきれていないため、ドキュメントどおり
database/*/table/*/*のワイルドカードとする(action は Get 系とUpdateTableのみ)Athena の Named Query は catalog を指定できないため、FROM 句を
"s3tablescatalog/..."で完全修飾する旧テーブル撤去までは、残存している
iceberg/prefix 分の S3 料金がかかるログの保持期間を S3 Lifecycle で切れない。古い行を消す必要が出た場合は Athena の
DELETEを定期実行する(現状のデータ量では消さない)
見直す条件
他用途で table bucket を追加する場合、
s3tablescatalogを共通スタックへ切り出す列単位・行単位のアクセス制御が必要になった場合は案 C(Lake Formation)を再検討する
S3 Tables の月額が想定(月数セント〜数十セント)を大きく超えた場合は Cost Explorer で内訳を確認する
保持期間による削除が必要になった場合や、S3 Tables 固有の運用が負担になった場合は案 D(gzip JSONL + S3 Lifecycle)を再検討する
構成
FireLens ──▶ Firehose (tidb-proxy-logs-s3tables)
├─ 正常 ─▶ S3 Tables: table bucket tidb-proxy-logs-tables
│ └─ namespace tidb_proxy_logs / table logs
└─ 失敗 ─▶ 汎用バケット tidb-proxy-logs-<account>/firehose-errors/
Athena ──▶ "s3tablescatalog/tidb-proxy-logs-tables"."tidb_proxy_logs"."logs"
リソース |
設定 |
|---|---|
|
|
|
|
|
|
|
|
Firehose |
|
Firehose ロール |
s3tables はこの table bucket / table に限定。Glue は Firehose 開発者ガイドの ARN どおり |
Athena Named Query |
NamedQuery に catalog 指定が無いため FROM 句で完全修飾 |
デプロイロール |
|
Firehose は S3 Tables 宛てではテーブルを自動作成しないため、スキーマ付きの AWS::S3Tables::Table を CDK で先に作る。
デプロイ前の確認
IAM だけでの権限管理は「一部リージョン」での提供と発表されている。東京リージョン (ap-northeast-1) が対象に含まれることを、上記 What's New からリンクされているドキュメントで確認する。
s3tablescatalog はアカウント・リージョンで 1 つ。旧来のコンソール手順(Lake Formation 方式)で統合済みの場合、AWS::Glue::Catalog の作成が名前衝突で失敗する。
# NotFound なら CDK で作成してよい。存在する場合は CatalogInput と作成経緯を確認する
aws glue get-catalog --catalog-id s3tablescatalog
# Lake Formation に s3tablescatalog のデータロケーションが登録されていないこと
aws lakeformation list-resources --query 'ResourceInfoList[?contains(ResourceArn, `s3tables`)]'
replacement の有無は cdk diff(既定の change set 方式)で確認する。--no-change-set / --method=template は template の差分しか見ないため、リソースハンドラ側の replacement(今回の Firehose CatalogArn 変更など)を検出できない。
cd iac/aws
bunx dotenv -- cdk deploy -c stageName=dev d-st-deploy-role --require-approval never
bunx dotenv -- cdk diff -c stageName=dev st-tidb-proxy-logs
確認結果 (2026-10-06)
項目 |
結果 |
|---|---|
|
|
Lake Formation 登録リソース |
なし |
Lake Formation data lake settings |
Admin なし、DB / Table のデフォルト権限は |
S3 Tables table bucket |
なし |
Firehose 現行 |
|
日次 VACUUM |
2026-10-06 03:00 JST 実行分まで |
|
DeliveryStream は |
cdk diff のその他の差分は、S3 Tables / s3tablescatalog の追加、VACUUM 関連 7 リソースの削除、Firehose ロールのポリシー更新、Named Query 3 件の replace(QueryString 変更による。名前は同じで作り直されるだけ)。旧 GlueDatabase / LogsTable は残る。
デプロイ経緯 (2026-10-06)
時刻 (JST) |
操作 |
結果 |
|---|---|---|
15:01 |
既存 stream |
|
15:06 |
新 stream を並走させる構成でデプロイ |
|
15:10 |
table bucket 名を |
成功。新 stream |
15:15 |
|
|
15:34 |
旧 stream / 旧 Firehose ロールを CDK から削除してデプロイ |
成功。旧 stream のバッファ(15:27 の 5 分値で最後の 10 行)が flush 済みであることを確認してから削除。旧 log stream |
学び:
Firehose の
IcebergDestinationConfiguration.CatalogConfiguration.CatalogArn変更は replacement になる。固定名の stream は別名で並走させて切り替えるS3 Tables の table bucket は削除直後しばらく同名で作り直せない。ロールバックで消えた名前は再利用せず別名にする
ecspresso の差分は
FIREHOSE_DELIVERY_STREAMのみ。ほか 2 件(healthCheckGracePeriodSeconds: 0/ log-router のuser: "0")は AWS 側のデフォルト値の表示差分
デプロイと動作確認
bunx dotenv -- cdk deploy -c stageName=dev st-tidb-proxy-logs --require-approval never
# バッファ間隔 (900 秒) 経過後に S3 Tables 側へ行が入ること
aws athena start-query-execution \
--work-group tidb-proxy-logs \
--query-string 'SELECT ts, log_type, level, method, url, status FROM "s3tablescatalog/tidb-proxy-logs-tables"."tidb_proxy_logs"."logs" ORDER BY ts DESC LIMIT 20'
# 配信失敗レコードが増えていないこと
aws s3 ls s3://tidb-proxy-logs-$(aws sts get-caller-identity --query Account --output text)/firehose-errors/ --recursive
動作確認結果 (2026-10-06 15:33)
項目 |
結果 |
|---|---|
|
41(15:27 の 5 分値)。 |
Firehose エラーログ / |
どちらも出力なし |
Athena |
41 行。先頭は 15:15:53 JST(新 task 起動時刻)、うち |
Named Query 3 件 |
作り直し後も名前・説明文は正常 |
東京リージョンでの IAM アクセス制御モード |
|
料金確認 (2026-10-07 07:15 JST)
Cost Explorer(UTC 日次、Amazon S3):
日 |
S3 合計 |
内訳 |
|---|---|---|
10/01〜10/05(切り替え前) |
約 $0.0062 |
TimedStorage \(0.0021 / Requests-Tier1 \)0.0019(約 415 件)/ Requests-Tier2 $0.0022(約 5,850 件) |
10/06(15:15 JST 切り替え) |
$0.0051 |
通常 S3 \(0.0040 + `APN1-Tables-Requests-Tier1` \)0.00078(167 件)/ |
S3 Tables の状態(切り替えから約 16 時間):
項目 |
値 |
|---|---|
行数 |
2,027 |
data files( |
13 個 / 72 KB(compaction 済み。最終実行 2026-10-07 06:59 JST Successful) |
snapshots( |
58 個(約 90 個/日。14 日保持で約 1,300 個で頭打ちの想定) |
snapshot management |
Successful(2026-10-06 23:53 JST) |
unreferenced file removal |
Not_Yet_Run(unreferenced 3 日 + noncurrent 10 日のため現時点では正常) |
汎用バケット容量 |
約 2.04 GB(旧 |
所見:
S3 日額は切り替え前と同程度かやや減。旧テーブルへの書き込みと VACUUM の GET が止まり、代わりに S3 Tables のリクエスト料金(約 $0.002〜0.003/日の見込み)が乗る
S3 Tables の storage / monitoring / compaction はまだ請求に出ない規模
要確認: snapshot 数に比例して大きくなる metadata JSON の旧版が unreferenced file removal で消えるか。10/10 以降に Cost Explorer の S3 Tables storage が横ばいであることを確認する
旧テーブルの撤去 (2026-10-07)
切り替え後 1 日の動作・料金確認を経て、同じ PR で撤去した。
時刻 (JST) |
操作 |
結果 |
|---|---|---|
07:20 |
事前確認 |
汎用バケットはバージョニング無効。 |
07:23 |
CDK から |
|
07:23〜07:28 |
|
0 objects。残る prefix は |
07:28 |
CFN 管理外で残っていた旧 log stream |
log group には |
Named Query の Database プロパティ(tidb_proxy_logs)は default catalog に実体が無くなったが、FROM 句を完全修飾しているため実行に影響しない(recent-activity を Catalog=AwsDataCatalog,Database=tidb_proxy_logs のコンテキストで実行して SUCCEEDED を確認)。
デプロイロールの反映 (2026-10-07)
CI (reusable-deploy.yaml) は CDK bootstrap ロールを assume できず(current credentials could not be used to assume ... Proceeding anyway)、{dev,prd}-shuntaka-assume-role の権限で直接 CloudFormation を実行している。ローカル確認時点で、AWS 上の deploy role はコードより古かった。
ロール |
コードにあって AWS に未反映だった権限 |
|---|---|
|
|
|
|
2026-09-28 のリリースで prd が通っていたのは、該当スタックが no changes だったため。S3 Tables を含むスタックを CI から更新できるよう、07:40 台に両ロールをローカルからデプロイし、inline policy に上記がすべて入ったことを確認した。deploy role は CI のデプロイ対象に含まれないため、権限を追加したら手動で反映する(01_development.md の「GitHub Actions用のデプロイロールの作成」を参照)。
料金の目安
S3 Tables はストレージ単価が S3 Standard より約 15% 高い(us-east-1 で \(0.0265/GB-月)ほか、オブジェクト監視(\)0.025 / 1,000 objects)と compaction(\(0.002 / 1,000 objects + \)0.005 / GB)が課金される。実データは数百 MiB 規模のため月数セント程度の見込み。東京リージョンの正確な単価は Pricing Calculator で確認する。