Aurora→Redshift Zero-ETL、6ヶ月本番運用して見えた落とし穴と設計の勘どころ

「ETLのメンテはもう嫌だ」とZero-ETL統合を本番投入して6ヶ月。40%高速化できた一方で何度も頭を抱えた現実を、試行錯誤そのままにまとめました。

Aurora→Redshift Zero-ETL統合を本番6ヶ月運用して見えた現実|落とし穴と設計の勘どころ

Aurora→Redshift Zero-ETLを本番投入して6ヶ月、正直に振り返る

去年の年末に「もうバッチETLのメンテは嫌だ」という話がチームで出て、Aurora MySQL → Redshift のZero-ETL統合を本番に入れることにした。結論から言うと、想像より40%速くアナリティクスが回るようになった半面、「思ってたより複雑だ」と何度も頭を抱えることになった。

Zero-ETLはAWSが2023年末にGA(正式リリース)して、2024〜2025年と機能拡張が続き、2026年現在はかなり成熟してきた印象だ。ただ、「ETLが”ゼロ”になる」という表現は少し誇張気味で、実際には「変換ロジックはゼロにはならない、移動のコードがゼロになる」という理解が正確だと感じている。

良い面も悪い面も、6ヶ月かけて試行錯誤した内容をそのまままとめた。なお、以前書いたAurora→Redshift Zero-ETL、4時間のバッチが15分になった話Aurora→Redshift間のZero-ETL統合を本番導入してわかったことと重複しないよう、今回はより深い運用知見にフォーカスしていく。


うちのシステム構成と導入前の状況

導入前のパイプラインはこんな感じだった。

  • Aurora MySQL 8.0(トランザクションDB)
  • 毎夜0時にPythonスクリプトでCSVエクスポート
  • S3経由でRedshiftにCOPYコマンドでロード
  • 朝9時のダッシュボード確認時点でデータが「昨日まで」しか見えない

これが毎朝のミーティングで「データが古い」という不満の種になっていた。アナリストからは「せめてリアルタイムに近いデータを見たい」という要望が何度も出ていた。

Zero-ETL統合を入れることで、ほぼリアルタイムでAurora上の変更がRedshiftに反映される。理論上は数秒〜数分のラグで済む。これを本番に入れた、というのが今回の話だ。

本番環境の全体構成図

graph TB
    subgraph VPC["VPC (ap-northeast-1)"]
        subgraph AZ1["AZ1 (ap-northeast-1a)"]
            subgraph AuroraCluster["Aurora Cluster"]
                AuroraPrimary["Aurora MySQL 8.0\nPrimary"]
                AuroraReplica["Aurora MySQL 8.0\nRead Replica"]
            end
        end
        subgraph AZ2["AZ2 (ap-northeast-1c)"]
            subgraph AuroraCluster2["Aurora Cluster (Standby)"]
                AuroraStandby["Aurora MySQL 8.0\nStandby"]
            end
        end
        subgraph AppLayer["Application Layer"]
            AppServer["ECS Fargate\nAPIサーバー"]
            LambdaProc["Lambda\n変換ロジック"]
        end
    end

    subgraph ZeroETL["Zero-ETL Integration"]
        ZeroETLLink["AWS Zero-ETL\nIntegration"]
    end

    subgraph RedshiftLayer["Redshift (Serverless)"]
        RSNamespace["Namespace\n(本番ワークスペース)"]
        RSWorkgroup["Workgroup\n(クエリエンドポイント)"]
        RSTargetDB["Target DB\n(ゼロETLレプリカ)"]
    end

    subgraph Analytics["Analytics Layer"]
        QuickSight["QuickSight\nダッシュボード"]
        dbtCloud["dbt Cloud\n変換モデル"]
        DataZone["Amazon DataZone\nデータカタログ"]
    end

    subgraph Monitoring["監視"]
        CloudWatch["CloudWatch\nメトリクス"]
        EventBridge["EventBridge\nアラート"]
    end

    AppServer -->|"書き込み"| AuroraPrimary
    AuroraPrimary -->|"バイナリログ"| ZeroETLLink
    ZeroETLLink -->|"自動レプリケーション"| RSTargetDB
    RSTargetDB --- RSNamespace
    RSNamespace --- RSWorkgroup
    RSWorkgroup -->|"クエリ"| dbtCloud
    dbtCloud -->|"集計モデル"| QuickSight
    RSTargetDB -->|"メタデータ"| DataZone
    LambdaProc -->|"後処理"| RSWorkgroup
    AuroraPrimary --> CloudWatch
    ZeroETLLink --> CloudWatch
    CloudWatch --> EventBridge

RedshiftはServerlessを使っている。理由は単純で、分析クエリの実行パターンが不均一で、日中ピーク時と深夜の差が激しいためだ。Redshift Serverless運用で月50万円→18万円に削減した話でも書いたが、WPU管理さえちゃんとやればServerlessは意外とコスパが良い。


導入手順と最初の1ヶ月で引っかかったこと

Zero-ETL統合の有効化手順

まずAurora側の設定から。ポイントはbinlog形式とパラメータグループの設定だ。

-- Aurora MySQL側の確認
SHOW VARIABLES LIKE 'binlog_format';
-- ROW になっていることを必ず確認

SHOW VARIABLES LIKE 'binlog_row_image';
-- FULL になっていることを確認

AWSコンソールまたはCLIでZero-ETL統合を作成する。

# Zero-ETL統合の作成
aws rds create-integration \
  --source-arn arn:aws:rds:ap-northeast-1:123456789012:cluster:prod-aurora-cluster \
  --target-arn arn:aws:redshift-serverless:ap-northeast-1:123456789012:namespace/prod-namespace \
  --integration-name prod-zero-etl \
  --kms-key-id arn:aws:kms:ap-northeast-1:123456789012:key/xxxx-xxxx \
  --region ap-northeast-1

Redshift側ではターゲットデータベースを作成する。

-- Redshift Serverlessに接続後
CREATE DATABASE aurora_replica
FROM INTEGRATION 'xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
DATABASE 'prod_db';

最初につまずいたのはここで、DATASHAREの権限がないと接続後のDB作成でエラーになる。具体的にはRedshiftのIAMロールにAurora側からのアクセス許可リソースポリシーが必要で、これが公式ドキュメントでわかりにくい書き方をされていてハマった。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "redshift.amazonaws.com"
      },
      "Action": "redshift:AuthorizeDataShare",
      "Resource": "arn:aws:redshift:ap-northeast-1:123456789012:datashare:*/aurora_replica"
    }
  ]
}

初期ロードの罠

Zero-ETL統合を有効化すると、まず全データの初期ロード(Full Load)が走る。うちの場合、Aurora上の対象テーブルの合計サイズが約300GBあったため、初期ロードに7時間かかった

この間、Redshiftのターゲットデータベースはクエリ不可の状態になる。アナリティクスチームへの事前周知を忘れて、朝9時のダッシュボードが「データなし」になって大騒ぎになった。これは純粋に僕の連絡ミスだった。反省している。

初期ロードの進捗はCloudWatchで確認できる。

# 初期ロード進捗の確認
aws cloudwatch get-metric-statistics \
  --namespace AWS/RDS \
  --metric-name ZeroETLReplicationSlotDiskUsage \
  --dimensions Name=DBClusterIdentifier,Value=prod-aurora-cluster \
  --start-time 2026-01-15T00:00:00Z \
  --end-time 2026-01-15T10:00:00Z \
  --period 300 \
  --statistics Average

6ヶ月運用して見えたリアルなパフォーマンス

レプリケーションラグの実測値

6ヶ月間、レプリケーションラグ(秒)を計測し続けた。正直、最初の数字を見たときは「あ、これ結構かかってるな」と思ったが、地道にチューニングを続けた結果がこれだ。

xychart-beta
  title "Zero-ETL レプリケーションラグ(秒)月次平均"
  x-axis ["2026-02", "2026-03", "2026-04", "2026-05", "2026-06", "2026-07"]
  y-axis "ラグ(秒)" 0 --> 120
  bar [45, 38, 22, 18, 14, 12]
  line [45, 38, 22, 18, 14, 12]

最初の2ヶ月は設定が最適化されておらず、平均40秒超のラグが出ていた。Aurora側のbinlog_row_image設定の見直しと、Redshift側のWPUキャパシティ調整を繰り返した結果、6ヶ月目には平均12秒まで落ちた。業務用途では十分なリアルタイム性だと思う。

ただし、Aurora側で大量バルクインサートが走った直後はラグが一時的に120秒超えることがある。これはZero-ETLの既知の特性で、「バルクオペレーション中はラグが増える」という仕様だ。バッチ処理のスケジューリングと分析クエリのタイミングが重ならないよう、EventBridgeでバッチ完了後30分待ってからレポート生成するようにした。

旧バッチETLとの性能比較

数字で比べると、改善幅がはっきりわかる。

指標旧バッチETL(夜間Pythonスクリプト)Zero-ETL統合改善率
データ鮮度最大24時間遅延平均12秒ラグ7200倍
ETLスクリプト保守コスト月約20時間ほぼ0100%削減
障害発生頻度月平均2.3回月平均0.3回87%減
インフラコスト(パイプライン)月約8万円月約3.5万円56%削減
初期構築工数旧構成構築時3週間Zero-ETL設定1日大幅短縮

コスト面では、EC2上で動いていたPythonバッチサーバーが不要になり、S3への一時データ保存コストも消えた。その分Redshift Serverlessの料金は増えたが、総合的には月約4.5万円のコスト削減になった。個人的には「月20時間の保守工数がなくなった」というのが金額以上にでかい。


制約と落とし穴——正直に書く

Zero-ETLは全部解決してくれる魔法じゃない。運用してわかった制約をまとめておく。知っておくと後悔が減るはずだ。

テーブルの制約が意外と多い

Zero-ETL統合では、Aurora側のテーブルに以下の要件がある(2026年7月時点)。

-- Zero-ETL対象テーブルの要件チェック
-- 1. 主キーが必須
SHOW CREATE TABLE target_table;
-- PRIMARY KEY が存在すること

-- 2. サポートされているデータ型の確認
-- JSON型はサポート外(2026年時点でも制限あり)
-- GEOMETRY型も非サポート
-- ENUMは文字列として変換される

-- 主キーなしテーブルを確認するクエリ
SELECT 
  t.TABLE_NAME,
  t.TABLE_ROWS
FROM information_schema.TABLES t
LEFT JOIN information_schema.TABLE_CONSTRAINTS tc 
  ON t.TABLE_NAME = tc.TABLE_NAME 
  AND tc.CONSTRAINT_TYPE = 'PRIMARY KEY'
WHERE t.TABLE_SCHEMA = 'prod_db'
  AND tc.CONSTRAINT_NAME IS NULL;

うちのDBには主キーなしのログテーブルが3つあって、これがZero-ETLのスコープ外になってしまった。そのテーブルだけは引き続き旧来のバッチETLで処理する「ハイブリッド構成」になっている。正直まだ完全移行できていない。

DDL変更は要注意

Aurora側でDDL変更(ALTER TABLE)を実行すると、Zero-ETL統合が一時停止することがある。具体的にはカラム追加やカラム型変更が該当する。

-- こういう操作が問題になる
ALTER TABLE orders ADD COLUMN discount_rate DECIMAL(5,2) DEFAULT 0;

DDL変更後、Redshift側のターゲットテーブルにスキーマ変更が伝播するまで数分かかる。その間のデータ整合性をどう保証するかが課題で、うちでは以下のルールを設けた。

  1. DDL変更は必ずメンテナンスウィンドウ(毎週水曜深夜2時)に実施
  2. 変更前にEventBridgeでアナリティクスダッシュボードに「メンテナンス中」バナーを表示
  3. DDL変更後はCloudWatchでラグが収束したことを確認してから完了とする

これを決めるまでに、無計画なALTER TABLEで2回ほど統合が止まる事態があった。チームへの共有も含め、手順書は早めに作っておくことを強く勧める。

dbtとの組み合わせで痛い目を見た

Zero-ETLでレプリケートされたデータはRedshift上では読み取り専用だ。dbt Cloudでモデルを構築する際、ターゲットDBに直接書き込もうとしてエラーになった。

# dbt project.yml の設定
models:
  my_project:
    staging:
      # Zero-ETLターゲットDBは読み取り専用
      # 別のスキーマに書き込む必要がある
      +schema: staging_transformed
      +database: analytics_db  # Zero-ETLターゲットとは別のDB

Zero-ETLのターゲットDBからSELECT、別のデータベースにCTAS(CREATE TABLE AS SELECT)する形にしてから安定した。データカタログ3回失敗して分かった、ようやく「使われる」ようになった話でも触れたが、データカタログとの統合もこの「レプリカDB→変換DB→公開DB」という3層構造を意識すると整理しやすい。


監視設計——ここを外すと後悔する

Zero-ETL統合は「つなげたら終わり」じゃない。継続的な監視が必要で、うちでは以下のCloudWatchアラームを設定している。

import boto3

cw = boto3.client('cloudwatch', region_name='ap-northeast-1')

# レプリケーションラグのアラーム
cw.put_metric_alarm(
    AlarmName='ZeroETL-ReplicationLag-High',
    MetricName='ExternalReplicationLagInSeconds',
    Namespace='AWS/Redshift',
    Statistic='Average',
    Period=300,  # 5分
    EvaluationPeriods=3,
    Threshold=300,  # 5分以上のラグで警告
    ComparisonOperator='GreaterThanThreshold',
    Dimensions=[
        {
            'Name': 'WorkgroupName',
            'Value': 'prod-workgroup'
        }
    ],
    AlarmActions=['arn:aws:sns:ap-northeast-1:123456789012:alert-topic'],
    TreatMissingData='breaching'  # データなし=問題として扱う
)

# 統合ステータスのチェック(Lambda定期実行)
def check_integration_status():
    rds = boto3.client('rds', region_name='ap-northeast-1')
    response = rds.describe_integrations(
        IntegrationIdentifier='prod-zero-etl'
    )
    
    integration = response['Integrations'][0]
    status = integration['Status']
    
    if status != 'ACTIVE':
        # Slack通知
        send_slack_alert(
            f"Zero-ETL統合が異常状態です: {status}\n"
            f"エラー: {integration.get('Errors', '不明')}"
        )
    
    return status

監視フローの全体像はこんな感じだ。

sequenceDiagram
    participant Aurora as Aurora MySQL
    participant ZeroETL as Zero-ETL
    participant Redshift as Redshift Serverless
    participant CW as CloudWatch
    participant Lambda as Lambda監視
    participant Slack as Slack

    Aurora->>ZeroETL: binlogイベント送信
    ZeroETL->>Redshift: データレプリケーション
    Redshift->>CW: ラグメトリクス送信
    
    loop 5分ごと
        CW->>CW: ラグ閾値チェック
    end
    
    CW-->>Slack: アラーム発火(ラグ>5分)
    
    loop 10分ごと
        Lambda->>ZeroETL: 統合ステータス確認
        ZeroETL-->>Lambda: ステータス返却
    end
    
    Lambda-->>Slack: 異常時のみSlack通知

特に**TreatMissingData='breaching'**の設定は重要で、これを忘れるとZero-ETL統合が停止してもアラームが発火しないという事態になる。実際に一度やらかして、3時間データが止まっていたことに気づかなかった苦い経験がある。「データが来ていない」という状態そのものを異常として検知する、という発想の転換が必要だった。

皆さんはZero-ETLの停止をどうやって検知してますか?もし良い方法があれば教えてほしい。


2026年時点の機能拡張と今後の見通し

2026年上半期に追加されたZero-ETL関連の機能で、実務で効いたものを挙げておく。

Aurora PostgreSQLのZero-ETL対応強化

2025年末にAurora PostgreSQL 16以降でZero-ETLのサポートが大幅に強化された。MySQL版と比べてデータ型サポート範囲が広がり、JSONBも部分的にサポートされるようになった。うちはMySQLメインだが、PostgreSQLを使っているチームにとっては大きなアップデートだと思う。

マルチソース統合

2026年Q1から、複数のAuroraクラスターを単一のRedshiftにZero-ETL統合できるようになった。

# 複数クラスターからの統合
# クラスター1(ユーザーDB)
aws rds create-integration \
  --source-arn arn:aws:rds:ap-northeast-1:123456789012:cluster:user-aurora-cluster \
  --target-arn arn:aws:redshift-serverless:ap-northeast-1:123456789012:namespace/prod-namespace \
  --integration-name user-zero-etl

# クラスター2(注文DB)
aws rds create-integration \
  --source-arn arn:aws:rds:ap-northeast-1:123456789012:cluster:order-aurora-cluster \
  --target-arn arn:aws:redshift-serverless:ap-northeast-1:123456789012:namespace/prod-namespace \
  --integration-name order-zero-etl

これ、地味に便利で、以前はユーザーDBと注文DBをJOINするためにS3経由で一度マージしていたが、今はRedshift上で直接クロスDB結合ができる。複数サービスのDBをまたいだ分析クエリが一気にシンプルになった。

Amazon DataZoneとの統合

DataZone本番運用3ヶ月の話とも絡むが、Zero-ETLで同期されたテーブルのメタデータが自動的にDataZoneのデータカタログに登録されるようになった。正直あまり期待していなかった機能だが、アナリストチームが「どのデータがリアルタイムでどのデータが昨日時点か」を自分で確認できるようになったのは想像以上の効果があった。

コスト感

現在の月額コスト内訳をまとめておく。

コスト項目月額(概算)備考
Zero-ETL統合(データ転送)約1.2万円300GB/日のレプリケーション
Redshift Serverless(WPU)約12万円平均64WPU
CloudWatchメトリクス・ログ約0.8万円カスタムメトリクス含む
Lambda監視関数約0.1万円10分間隔実行
合計約14.1万円旧構成比-4.5万円/月

Zero-ETL統合自体のデータ転送コストは思ったより安かった。これはAurora→Redshift間が同一AWSリージョン内のため、クロスリージョン転送コストが発生しないのが大きい。クロスリージョン構成を検討している方は要注意だ。


まとめ

6ヶ月の本番運用を振り返ると、Zero-ETLは「ETLが消える」ではなく「移動コストが消える」技術というのが正直な評価だ。Pythonのバッチスクリプト保守は確かにゼロになったが、dbtでの変換ロジックは依然として必要で、監視設計にも一定の工数がかかる。

それでもバッチETL時代の「朝のデータが昨日のもの」という制約が消えたことの業務インパクトは大きく、アナリストチームからの評判は間違いなく上がった。

運用を通じて辿り着いた要点を整理すると:

  1. 主キーなしテーブルはZero-ETLの対象外。事前にテーブル要件を確認し、非対応テーブルの代替手段を考えておく
  2. 初期ロードには想定以上の時間がかかる。300GBで7時間を覚悟し、メンテナンスウィンドウを確保する
  3. DDL変更は手順を決めておく。変更後のラグ収束確認まで含めた運用手順書が必須
  4. TreatMissingData='breaching'を忘れない。データ欠損=停止を検知するためのアラーム設定は最優先
  5. dbtとの組み合わせは「読み取りDB→変換DB」の2層構造で設計する

次にやりたいのは、Zero-ETLで同期されたデータをAmazon Bedrockと組み合わせてリアルタイム異常検知を実装することだ。まだ検証中だけど、できたらまた記事にする。

U

Untanbaby

ソフトウェアエンジニア|AWS / クラウドアーキテクチャ / DevOps

10年以上のIT実務経験をもとに、現場で使える技術情報を発信しています。 記事の誤りや改善点があればお問い合わせからお気軽にご連絡ください。

関連記事