【AWS CDK】AWS Glue zero-ETLでSalesforceデータをIceberg Tableに連携してみた

【AWS CDK】AWS Glue zero-ETLでSalesforceデータをIceberg Tableに連携してみた

2026.01.31

はじめに

データ事業本部のkasamaです。
今回は、AWS Glue Zero-ETLを使ってSalesforceからS3へデータ連携する仕組みを2025年11月からサポートされたAWS CDKで実装してみます。
https://aws.amazon.com/jp/about-aws/whats-new/2025/11/aws-glue-cloudformation-cdk-zero-etl-integrations/

前提

事前にSalesforceのDeveloper Editionに登録していることとします。まだの方は以下のドキュメントが参考になります。
https://developer.salesforce.com/docs/atlas.ja-jp.api_rest.meta/api_rest/quickstart_dev_org.htm

AppFlowとZero-ETLの比較

以前、Amazon AppFlowを使ったSalesforceからS3へのデータ連携をCDKで実装しました。AppFlowでもシンプルな実装で連携できたので、Zero-ETLのメリットは何なのか、AppFlowで十分ではないかと思い比較してみました。
https://dev.classmethod.jp/articles/cdk-appflow-salesforce-to-s3-data-load/

観点 Amazon AppFlow AWS Glue Zero-ETL
ユースケース ローコードSaaS連携 準リアルタイムETL
Salesforce対応 双方向(ソース/宛先) ソースのみ
データフォーマット Parquet, JSON, CSV等 Iceberg(ACIDトランザクション)
最低料金 $0.02/フロー実行 $0.44/時間
主な機能 豊富なデータソース、GUI CDC、増分同期、タイムトラベル
レイテンシ スケジュールまたはイベント駆動 継続同期(最小15分間隔)

比較表の通り、Zero-ETLにはAppFlowにはないメリットがあります。
直接Icebergに出力させることができ、同期方式は15分間隔から設定可能な継続的な同期です。AppFlowのスケジュール実行と比較して、より準リアルタイムに近いデータ連携が可能です。さらに、Change Data Capture(CDC)により、INSERT/UPDATE/DELETEの変更を自動検出して同期するため、差分同期が効率的に行えます。

一方で、コスト面では、Zero-ETLはデータ取り込みに$1.50/GB、S3への書き込み処理に$0.44/DPU時間の課金となります。AppFlowは$0.001/フロー実行、データ処理$0.02/GBから利用できるため、データ量や同期頻度によってはZero-ETLの方がコストが高くなる可能性があります。

https://aws.amazon.com/glue/pricing/
https://aws.amazon.com/appflow/pricing/

認証方式の選択

Salesforceとの接続には、JWT Bearer flowとAuthorization Code flowの二つのOAuth grant typeがあります。

https://docs.aws.amazon.com/glue/latest/dg/salesforce-configuring-connections.html

JWT Bearer flowはサーバー間認証でユーザー操作が不要なため、完全なIaCが可能です。一方、Authorization Code flowはブラウザでのユーザーログインが必要になります。

Authorization Code flowを選択した場合、さらにAWS Managed Connected AppとCustomer Managed Connected Appを選択できます。AWS Managed Connected AppはOAuth設定が不要でシンプルですが、コンソールでの操作が必要です。Customer Managed Connected AppはSalesforce側でConnected Appを作成し、クライアントID/シークレットを管理する必要がありますが、詳細な制御が可能です。

今回はAuthorization Code flowのAWS Managed Connected Appを採用しました。JWT Bearer flowであれば完全なIaCが可能ですが、Salesforce側での証明書設定やJWTトークンの管理が必要になります。AWS Managed Connected Appはコンソールでの操作が必要ですが、OAuthトークンの管理をAWSに委任できるため、設定がシンプルになります。

アーキテクチャ

CloudFormation、コンソール、CDKを組み合わせたデプロイ構成を採用しました。各リソースをデプロイ制約に応じて最適な方法で作成します。
salesforce-glue-zeroetl-architecture

CloudFormation (cfn/prerequisites.yaml)
  ├── S3 Data Lake Bucket
  ├── Source IAM Role
  ├── Glue Database
  └── Secrets Manager

AWS Console (手動)
  └── Salesforce Connection (AWS Managed App)

Shell Script (scripts/setup-prereqs.sh)
  └── Catalog Resource Policy

CDK (cdk/)
  ├── Target IAM Role
  ├── Integration Resource Property
  └── Zero-ETL Integration

リソースごとにデプロイ方法を分けた理由は、依存関係とサービスの制約によるものです。S3バケット、Source Role、Glue DatabaseはCDKデプロイ前に存在している必要があるため、CloudFormationで先に作成します。Salesforce ConnectionはAWS Managed AppのブラウザOAuth認証が必要なため、コンソールで作成します。Glue Data Catalog Resource PolicyはCloudFormationでサポートされていないため、AWS CLIを使ったシェルスクリプトで設定します。これらの前提リソースが揃った後、CDKでZero-ETL Integrationを作成します。

実装

実装コードはGitHubに格納しています。

https://github.com/cm-yoshikikasama/blog_code/tree/main/59_salesforce_glue_zeroetl

プロジェクト構成

59_salesforce_glue_zeroetl/
├── cdk/                    # CDKインフラコード
│   ├── bin/app.ts
│   ├── lib/
│   │   ├── parameter.ts
│   │   └── main-stack.ts
│   ├── package.json
│   └── tsconfig.json
├── cfn/
│   └── prerequisites.yaml  # CloudFormation前提リソース
├── scripts/
│   ├── config.sh
│   ├── setup-prereqs.sh
│   └── cleanup-prereqs.sh
└── README.md

CloudFormation

cfn/prerequisites.yamlでは、CDKデプロイ前に必要な前提リソースを作成します。

cfn/prerequisites.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: Prerequisites for Salesforce Zero-ETL Integration

Parameters:
  ProjectName:
    Type: String
    Default: my-sf-zeroetl
    Description: Project name prefix
  EnvName:
    Type: String
    Default: dev
    Description: Environment name

Resources:
  # ========================================
  # S3 Data Lake Bucket
  # ========================================
  DataLakeBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Delete
    Properties:
      BucketName: !Sub ${ProjectName}-${EnvName}-datalake
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true

  # ========================================
  # Secrets Manager (for OAuth token storage)
  # ========================================
  SalesforceOAuthSecret:
    Type: AWS::SecretsManager::Secret
    DeletionPolicy: Delete
    UpdateReplacePolicy: Delete
    Properties:
      Description: Stores OAuth tokens for Salesforce connection (auto-populated by AWS Glue)
      SecretString: "{}"

  # ========================================
  # Source IAM Role (for Salesforce Connection)
  # ========================================
  ZeroETLSourceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${ProjectName}-${EnvName}-source-role
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: glue.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: GlueConnectionAccess
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - glue:GetConnection
                  - glue:GetConnections
                  - glue:ListConnectionTypes
                  - glue:DescribeConnectionType
                  - glue:RefreshOAuth2Tokens
                  - glue:ListEntities
                  - glue:DescribeEntity
                Resource: "*"
        - PolicyName: SecretsManagerAccess
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - secretsmanager:DescribeSecret
                  - secretsmanager:GetSecretValue
                  - secretsmanager:PutSecretValue
                Resource: "*"

  # ========================================
  # Glue Database (for Zero-ETL target)
  # ========================================
  GlueDatabase:
    Type: AWS::Glue::Database
    Properties:
      CatalogId: !Ref AWS::AccountId
      DatabaseInput:
        Name: !Sub
          - ${ProjectNameUnderscore}_${EnvName}
          - ProjectNameUnderscore: !Join ["_", !Split ["-", !Ref ProjectName]]
        Description: Zero-ETL target database with Iceberg tables
        LocationUri: !Sub s3://${DataLakeBucket}/data/

Outputs:
  DataLakeBucketName:
    Value: !Ref DataLakeBucket
  SourceRoleArn:
    Value: !GetAtt ZeroETLSourceRole.Arn
  GlueDatabaseName:
    Value: !Sub
      - ${ProjectNameUnderscore}_${EnvName}
      - ProjectNameUnderscore: !Join ["_", !Split ["-", !Ref ProjectName]]
  SalesforceOAuthSecretName:
    Value: !Ref SalesforceOAuthSecret
    Description: Select this secret in Glue Console when creating Salesforce connection

S3バケットは、Glue DatabaseのLocationUriとして参照されるため先に作成する必要があります。Source RoleはSalesforce Connectionで指定するIAMロールで、Glue Connection操作とSecrets Manager操作の権限を持ちます。Secrets Managerは、コンソールでSalesforce Connection作成時にOAuthトークンの保存先として選択するため、事前に作成しておく必要があります。

Catalog Resource Policy設定スクリプト

Glue Data CatalogのResource PolicyはCloudFormationでサポートされていないため、シェルスクリプトで設定します。

scripts/setup-prereqs.sh
#!/bin/bash
set -e

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
source "${SCRIPT_DIR}/config.sh"

# Use REGION from config.sh if set, otherwise use AWS CLI default
if [ -z "$REGION" ]; then
  REGION=$(aws configure get region)
fi
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

echo "Setting Glue Catalog Resource Policy (Database: ${DATABASE_NAME})..."

# Glue Resource Policy has no CloudFormation support - must use CLI
cat > /tmp/catalog-policy.json << EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Service": "glue.amazonaws.com" },
      "Action": "glue:AuthorizeInboundIntegration",
      "Resource": "arn:aws:glue:${REGION}:${ACCOUNT_ID}:database/${DATABASE_NAME}"
    },
    {
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::${ACCOUNT_ID}:root" },
      "Action": "glue:CreateInboundIntegration",
      "Resource": "arn:aws:glue:${REGION}:${ACCOUNT_ID}:database/${DATABASE_NAME}"
    }
  ]
}
EOF

aws glue put-resource-policy --policy-in-json file:///tmp/catalog-policy.json
rm -f /tmp/catalog-policy.json

echo "Done."

このスクリプトでは、Zero-ETL Integrationの作成に必要な2つの権限を設定しています。

  • glue:AuthorizeInboundIntegration - Glueサービスがインバウンド統合を認可するための権限
  • glue:CreateInboundIntegration - アカウント内でインバウンド統合を作成するための権限

CDK実装

cdk/lib/parameter.ts
import type { Environment } from 'aws-cdk-lib';

export interface AppParameter {
  env?: Environment;
  envName: string;
  projectName: string;
  syncFrequencyMinutes: number;
  dataFilter: string;
}

export const devParameter: AppParameter = {
  envName: 'dev',
  projectName: 'my-sf-zeroetl',
  syncFrequencyMinutes: 15, // 15 minutes
  // syncFrequencyMinutes: 1440,  // 24 hours
  dataFilter: 'include:Account,include:Contact',
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region: process.env.CDK_DEFAULT_REGION,
  },
};

cdk/lib/parameter.tsでは、環境ごとのパラメータを定義しています。
dataFilterでは、Salesforceから同期するオブジェクトを指定しています。include:Account,include:Contactの形式で、AccountとContactオブジェクトを同期対象としています。これを設定しないでデプロイすると、デプロイ自体は成功し、dataFilterには*と入りますが、データ取り込みには失敗したので、Salesforceの場合は、必ず特定オブジェクトの指定が必要であると思います。

cdk/lib/main-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as glue from 'aws-cdk-lib/aws-glue';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as s3 from 'aws-cdk-lib/aws-s3';
import type { Construct } from 'constructs';
import type { AppParameter } from './parameter';

interface MainStackProps extends cdk.StackProps {
  parameter: AppParameter;
}

export class MainStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: MainStackProps) {
    super(scope, id, props);

    const { parameter } = props;

    // Resource naming conventions
    const dataLakeBucketName = `${parameter.projectName}-${parameter.envName}-datalake`;
    const databaseName = `${parameter.projectName.replace(/-/g, '_')}_${parameter.envName}`;
    const integrationName = `${parameter.projectName}-${parameter.envName}-integration`;
    const connectionName = `${parameter.projectName}-${parameter.envName}-salesforce-connection`;

    // Reference S3 bucket created by Prerequisites stack
    const dataLakeBucket = s3.Bucket.fromBucketName(this, 'DataLakeBucket', dataLakeBucketName);

    // External resource ARNs (created via CloudFormation / AWS Console)
    const databaseArn = `arn:aws:glue:${this.region}:${this.account}:database/${databaseName}`;
    const connectionArn = `arn:aws:glue:${this.region}:${this.account}:connection/${connectionName}`;
    const sourceRoleArn = `arn:aws:iam::${this.account}:role/${parameter.projectName}-${parameter.envName}-source-role`;

    // ========================================
    // 1. Target IAM Role (for S3/Glue Catalog)
    // ========================================
    const targetRole = new iam.Role(this, 'ZeroETLTargetRole', {
      roleName: `${parameter.projectName}-${parameter.envName}-target-role`,
      assumedBy: new iam.ServicePrincipal('glue.amazonaws.com'),
    });

    // S3 permissions
    dataLakeBucket.grantReadWrite(targetRole);

    // Glue Data Catalog permissions
    targetRole.addToPolicy(
      new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        actions: [
          'glue:GetDatabase',
          'glue:GetDatabases',
          'glue:GetTable',
          'glue:GetTables',
          'glue:CreateTable',
          'glue:UpdateTable',
          'glue:DeleteTable',
          'glue:GetPartitions',
          'glue:BatchCreatePartition',
          'glue:BatchDeletePartition',
        ],
        resources: [
          `arn:aws:glue:${this.region}:${this.account}:catalog`,
          `arn:aws:glue:${this.region}:${this.account}:database/${databaseName}`,
          `arn:aws:glue:${this.region}:${this.account}:table/${databaseName}/*`,
        ],
      })
    );

    // CloudWatch Logs permissions
    targetRole.addToPolicy(
      new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'],
        resources: [
          `arn:aws:logs:${this.region}:${this.account}:log-group:/aws-glue/*`,
          `arn:aws:logs:${this.region}:${this.account}:log-group:/aws-glue/*:*`,
        ],
      })
    );

    // ========================================
    // 2. Integration Resource Properties (source and target)
    // ========================================
    // Source: Salesforce Connection with Source Role
    const sourceResourceProperty = new glue.CfnIntegrationResourceProperty(
      this,
      'SourceResourceProperty',
      {
        resourceArn: connectionArn,
        sourceProcessingProperties: {
          roleArn: sourceRoleArn,
        },
      }
    );

    // Target: Glue Database with Target Role
    const targetResourceProperty = new glue.CfnIntegrationResourceProperty(
      this,
      'TargetResourceProperty',
      {
        resourceArn: databaseArn,
        targetProcessingProperties: {
          roleArn: targetRole.roleArn,
        },
      }
    );

    // ========================================
    // 3. Zero ETL Integration
    // ========================================
    // Note: Salesforce Connection must be created via AWS Console before CDK deploy
    const zeroEtlIntegration = new glue.CfnIntegration(this, 'ZeroETLIntegration', {
      integrationName: integrationName,
      sourceArn: connectionArn,
      targetArn: databaseArn,
      description: 'Salesforce to S3 (Iceberg) Zero-ETL Integration',
      dataFilter: parameter.dataFilter,

      integrationConfig: {
        continuousSync: true,
        refreshInterval: `${parameter.syncFrequencyMinutes}`,
      },
      tags: [
        { key: 'Environment', value: parameter.envName },
        { key: 'DataFormat', value: 'Iceberg' },
      ],
    });

    zeroEtlIntegration.node.addDependency(sourceResourceProperty);
    zeroEtlIntegration.node.addDependency(targetResourceProperty);

    // ========================================
    // Outputs
    // ========================================
    new cdk.CfnOutput(this, 'IntegrationArn', {
      value: zeroEtlIntegration.attrIntegrationArn,
      description: 'Zero-ETL Integration ARN',
    });

    new cdk.CfnOutput(this, 'TargetRoleArn', {
      value: targetRole.roleArn,
      description: 'Target Role ARN',
    });
  }
}

cdk/lib/main-stack.tsでは、Target RoleとZero-ETL Integrationを作成します。

  • Target Role
    • S3への読み書き権限
    • Glue Data Catalogへのテーブル作成・更新権限
    • CloudWatch Logsへの書き込み権限
  • CfnIntegrationResourceProperty
    • ソース側(Salesforce Connection + Source Role)
    • ターゲット側(Glue Database + Target Role)
  • CfnIntegration
    • Zero-ETL統合本体
    • continuousSync: trueで継続同期を有効化
    • refreshIntervalで同期間隔を指定(分単位)
    • dataFilterで同期対象オブジェクトを指定

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_glue.CfnIntegrationResourceProperty.html
https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_glue.CfnIntegration.html

デプロイ

CloudFormationリソース

S3バケット、Source Role、Glue Databaseを作成します。

aws cloudformation deploy \
  --template-file cfn/prerequisites.yaml \
  --stack-name my-sf-zeroetl-dev-prerequisites \
  --capabilities CAPABILITY_NAMED_IAM \
  --profile YOUR_AWS_PROFILE

AWSコンソールでSalesforce Connectionを作成

以下の手順でSalesforce Connectionを生成します。

AWS Glueコンソール > Data Catalog > Connections に移動し、「Create connection」をクリックし、「Salesforce」を選択します。
Screenshot 2026-01-18 at 14.55.25

以下の設定値を入力し、「Test Connection」を実行します。

  • Instance URL: https://<your-domain>.my.salesforce.com(SalesforceログインURL)
  • Salesforce environment: Production
  • IAM service role: my-sf-zeroetl-dev-source-role
  • OAuth grant type: Authorization Code
  • 「Use AWS managed client application」にチェック
  • AWS Secret: CloudFormation OutputsのSalesforceOAuthSecretNameを選択

Screenshot 2026-01-18 at 15.07.29
Salesforceにログイン済みであれば、ログインユーザーへのアクセス許可ポップアップが表示されます。問題なければ許可してください。
Screenshot 2026-01-18 at 15.10.19
許可後にAWS画面に戻ると接続が成功した状態になります。
Screenshot 2026-01-18 at 15.10.53

my-sf-zeroetl-dev-salesforce-connectionの形式でconnection nameを入力し、作成完了です。

Catalog Resource Policyの設定

以下のコマンドでCatalog Resource Policyを設定します。

cd scripts
AWS_PROFILE=YOUR_AWS_PROFILE ./setup-prereqs.sh

CDKデプロイ

次にcdkデプロイコマンドでZero-ETL統合を作成します。

cd cdk
pnpm install
pnpm cdk deploy --all --require-approval never --profile YOUR_AWS_PROFILE

デプロイ後確認

AWS GlueコンソールでZero-ETL integrationsに移動し、作成したIntegrationのステータスが「Active」になっていることを確認します。
Screenshot 2026-01-18 at 15.21.01

SELECT * FROM "my_sf_zeroetl_dev"."zetl_integration_table_state" limit 10;

zetl_integration_table_stateにtableごとのinsert,update,deleteされたレコードのcountが記録されるようです。
Screenshot 2026-01-18 at 16.15.47
contact, account tableともにデータが取得できています。
Screenshot 2026-01-18 at 16.17.11
Screenshot 2026-01-18 at 16.16.55

S3のデータも確認してみました。3 TableともにIceberg形式で管理されていることがわかります。

s3 ls
aws s3 ls s3://my-sf-zeroetl-dev-datalake/ --recursive
2026-01-18 06:26:00      22369 data/account/data/id_bucket=0/00000-43-887a92b7-99b4-4289-88aa-d0ddd98fd7fd-0-00001.parquet
2026-01-18 06:26:00      22203 data/account/data/id_bucket=10/00000-43-887a92b7-99b4-4289-88aa-d0ddd98fd7fd-0-00004.parquet
2026-01-18 06:26:00      22369 data/account/data/id_bucket=0/00000-43-887a92b7-99b4-4289-88aa-d0ddd98fd7fd-0-00001.parquet
2026-01-18 06:26:00      22203 data/account/data/id_bucket=10/00000-43-887a92b7-99b4-4289-88aa-d0ddd98fd7fd-0-00004.parquet
2026-01-18 06:26:00      21992 data/account/data/id_bucket=11/00000-43-887a92b7-99b4-4289-88aa-d0ddd98fd7fd-0-00005.parquet
2026-01-18 06:26:00      17105 data/account/data/id_bucket=12/00000-43-887a92b7-99b4-4289-88aa-d0ddd98fd7fd-0-00006.parquet
2026-01-18 06:25:58      20172 data/account/data/id_bucket=13/00001-44-887a92b7-99b4-4289-88aa-d0ddd98fd7fd-0-00001.parquet
2026-01-18 06:26:00      22438 data/account/data/id_bucket=14/00000-43-887a92b7-99b4-4289-88aa-d0ddd98fd7fd-0-00007.parquet
2026-01-18 06:26:00      22781 data/account/data/id_bucket=15/00000-43-887a92b7-99b4-4289-88aa-d0ddd98fd7fd-0-00008.parquet
2026-01-18 06:26:00      21402 data/account/data/id_bucket=17/00000-43-887a92b7-99b4-4289-88aa-d0ddd98fd7fd-0-00009.parquet
2026-01-18 06:26:00      20842 data/account/data/id_bucket=2/00000-43-887a92b7-99b4-4289-88aa-d0ddd98fd7fd-0-00002.parquet
2026-01-18 06:26:00      22103 data/account/data/id_bucket=21/00000-43-887a92b7-99b4-4289-88aa-d0ddd98fd7fd-0-00010.parquet
2026-01-18 06:26:01      22620 data/account/data/id_bucket=25/00000-43-887a92b7-99b4-4289-88aa-d0ddd98fd7fd-0-00011.parquet
2026-01-18 06:26:00      18800 data/account/data/id_bucket=8/00000-43-887a92b7-99b4-4289-88aa-d0ddd98fd7fd-0-00003.parquet
2026-01-18 06:26:05       9482 data/account/metadata/00000-4b06aead-151f-40f7-925c-194a5cb45e43.metadata.json
2026-01-18 06:26:05      14882 data/account/metadata/84df3740-a022-4cae-876b-12043faa9303-m0.avro
2026-01-18 06:26:05       4252 data/account/metadata/snap-7528348945578359596-1-84df3740-a022-4cae-876b-12043faa9303.avro
2026-01-18 06:25:19      18177 data/contact/data/id_bucket=1/00000-15-2137d242-4d9f-4489-9593-e0cf6c71dee7-0-00001.parquet
2026-01-18 06:25:19      19273 data/contact/data/id_bucket=10/00000-15-2137d242-4d9f-4489-9593-e0cf6c71dee7-0-00005.parquet
2026-01-18 06:25:19      18891 data/contact/data/id_bucket=12/00000-15-2137d242-4d9f-4489-9593-e0cf6c71dee7-0-00006.parquet
2026-01-18 06:25:19      18757 data/contact/data/id_bucket=14/00000-15-2137d242-4d9f-4489-9593-e0cf6c71dee7-0-00007.parquet
2026-01-18 06:25:19      19379 data/contact/data/id_bucket=17/00000-15-2137d242-4d9f-4489-9593-e0cf6c71dee7-0-00008.parquet
2026-01-18 06:25:19      19125 data/contact/data/id_bucket=19/00000-15-2137d242-4d9f-4489-9593-e0cf6c71dee7-0-00009.parquet
2026-01-18 06:25:20      19114 data/contact/data/id_bucket=20/00000-15-2137d242-4d9f-4489-9593-e0cf6c71dee7-0-00010.parquet
2026-01-18 06:25:20      19057 data/contact/data/id_bucket=22/00000-15-2137d242-4d9f-4489-9593-e0cf6c71dee7-0-00011.parquet
2026-01-18 06:25:20      19476 data/contact/data/id_bucket=24/00000-15-2137d242-4d9f-4489-9593-e0cf6c71dee7-0-00012.parquet
2026-01-18 06:25:20      18973 data/contact/data/id_bucket=25/00000-15-2137d242-4d9f-4489-9593-e0cf6c71dee7-0-00013.parquet
2026-01-18 06:25:20      19059 data/contact/data/id_bucket=27/00000-15-2137d242-4d9f-4489-9593-e0cf6c71dee7-0-00014.parquet
2026-01-18 06:25:20      18693 data/contact/data/id_bucket=30/00000-15-2137d242-4d9f-4489-9593-e0cf6c71dee7-0-00015.parquet
2026-01-18 06:25:20      19159 data/contact/data/id_bucket=31/00000-15-2137d242-4d9f-4489-9593-e0cf6c71dee7-0-00016.parquet
2026-01-18 06:25:19      16564 data/contact/data/id_bucket=6/00000-15-2137d242-4d9f-4489-9593-e0cf6c71dee7-0-00002.parquet
2026-01-18 06:25:19      18789 data/contact/data/id_bucket=8/00000-15-2137d242-4d9f-4489-9593-e0cf6c71dee7-0-00003.parquet
2026-01-18 06:25:19      20324 data/contact/data/id_bucket=9/00000-15-2137d242-4d9f-4489-9593-e0cf6c71dee7-0-00004.parquet
2026-01-18 06:25:25       9154 data/contact/metadata/00000-0ef9e4c5-80a0-4bc3-8771-5de2c4f43223.metadata.json
2026-01-18 06:25:24      15094 data/contact/metadata/b2f23ae0-337a-4078-93f9-e74a69cda51f-m0.avro
2026-01-18 06:25:25       4251 data/contact/metadata/snap-1009671950301006008-1-b2f23ae0-337a-4078-93f9-e74a69cda51f.avro
2026-01-18 06:26:20       3534 data/zetl_integration_table_state/data/00000-80-b7f0862a-7147-48a4-834e-26fd0a2fd610-0-00001.parquet
2026-01-18 06:25:29       3449 data/zetl_integration_table_state/data/00015-31-59ebc11f-f5b6-4f7a-a8ce-9a0de184ab82-0-00001.parquet
2026-01-18 06:25:38       2979 data/zetl_integration_table_state/metadata/00000-b0cd506f-00e1-4a4f-9de8-66dc123f52ce.metadata.json
2026-01-18 06:26:20       4078 data/zetl_integration_table_state/metadata/00001-e5b837fe-39c4-439b-b379-2db3b25008d7.metadata.json
2026-01-18 06:26:20       7408 data/zetl_integration_table_state/metadata/244b9cc2-d32a-481e-9ede-d45422cdcc3f-m0.avro
2026-01-18 06:25:38       7413 data/zetl_integration_table_state/metadata/638fc5be-1273-422a-b512-fd32c12f0e7c-m0.avro
2026-01-18 06:26:20       4325 data/zetl_integration_table_state/metadata/snap-2216260906034196567-1-244b9cc2-d32a-481e-9ede-d45422cdcc3f.avro
2026-01-18 06:25:38       4255 data/zetl_integration_table_state/metadata/snap-2858764749464102138-1-638fc5be-1273-422a-b512-fd32c12f0e7c.avro

増分同期の確認(UPDATE/DELETE)

最後にSalesforceでの変更が自動的に同期されることを確認します。
Salesforceの画面で、アプリケーションランチャーから取引先を選択します。
任意のレコードをUPDATEとDELETEします。
Screenshot 2026-01-18 at 16.30.23
UPDATE
Screenshot 2026-01-18 at 16.30.04
DELETE
Screenshot 2026-01-18 at 16.32.32
時間が経ってから確認するとaccount tableのUPDATEとDELETE countが1増えていました。
Screenshot 2026-01-18 at 22.08.56
account tableをselectすると、対象レコードはUPDATEされて、DELETEレコードは表示されないことを確認しました。
Screenshot 2026-01-18 at 22.12.47

最後に

一度仕組みを構築すれば、ETLスクリプトなしでCDCデータ取り込みがIceberg Tableに対して実行できる便利な機能です。ただし、コスト面はAppFlowなど他サービスと比較検証する必要があります。参考になれば幸いです。

この記事をシェアする

FacebookHatena blogX

関連記事