【AWS CDK】AWS Glue zero-ETLでSalesforceデータをIceberg Tableに連携してみた
はじめに
データ事業本部のkasamaです。
今回は、AWS Glue Zero-ETLを使ってSalesforceからS3へデータ連携する仕組みを2025年11月からサポートされたAWS CDKで実装してみます。
前提
事前にSalesforceのDeveloper Editionに登録していることとします。まだの方は以下のドキュメントが参考になります。
AppFlowとZero-ETLの比較
以前、Amazon AppFlowを使ったSalesforceからS3へのデータ連携をCDKで実装しました。AppFlowでもシンプルな実装で連携できたので、Zero-ETLのメリットは何なのか、AppFlowで十分ではないかと思い比較してみました。
| 観点 | 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の方がコストが高くなる可能性があります。
認証方式の選択
Salesforceとの接続には、JWT Bearer flowとAuthorization Code flowの二つのOAuth grant typeがあります。
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を組み合わせたデプロイ構成を採用しました。各リソースをデプロイ制約に応じて最適な方法で作成します。

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に格納しています。
プロジェクト構成
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デプロイ前に必要な前提リソースを作成します。
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でサポートされていないため、シェルスクリプトで設定します。
#!/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実装
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の場合は、必ず特定オブジェクトの指定が必要であると思います。
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で同期対象オブジェクトを指定
デプロイ
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」を選択します。

以下の設定値を入力し、「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を選択

Salesforceにログイン済みであれば、ログインユーザーへのアクセス許可ポップアップが表示されます。問題なければ許可してください。

許可後にAWS画面に戻ると接続が成功した状態になります。

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」になっていることを確認します。

SELECT * FROM "my_sf_zeroetl_dev"."zetl_integration_table_state" limit 10;
zetl_integration_table_stateにtableごとのinsert,update,deleteされたレコードのcountが記録されるようです。

contact, account tableともにデータが取得できています。


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します。

UPDATE

DELETE

時間が経ってから確認するとaccount tableのUPDATEとDELETE countが1増えていました。

account tableをselectすると、対象レコードはUPDATEされて、DELETEレコードは表示されないことを確認しました。

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








