【アップデート】 Amazon Aurora DSQL の Change Data Capture が一般提供になりました
ウィスキー、シガー、パイプをこよなく愛する大栗です。
2026 年 5 月にプレビューとして発表された Amazon Aurora DSQL の Change Data Capture(CDC)が、2026 年 7 月 8 日に一般提供(GA)になりました!プレビュー時に最大の注意点だった「INSERT と UPDATE が区別できない」問題も解消されているので、変更点を中心にご紹介します。
プレビュー時の内容は以下のエントリをご覧ください。
Aurora DSQL の CDC とは
Aurora DSQL の CDC は、コミット済みのデータベース変更を準リアルタイムで Amazon Kinesis Data Streams に配信する機能です。クラスター内のすべてのユーザーテーブルに対する INSERT・UPDATE・DELETE の結果を行レベルで自動キャプチャし、構造化された JSON レコードとして配信します。
主な特徴は以下の通りです。
| 項目 | 内容 |
|---|---|
| 配信先 | Amazon Kinesis Data Streams(BYOT: Bring Your Own Target モデル) |
| 対象 | クラスター内の全ユーザーテーブルの行レベル変更 |
| レコード形式 | 構造化 JSON |
| 配信保証 | 少なくとも 1 回(at-least-once)。同じレコードが複数回届くことがある |
| 順序 | UNORDERED(コミット順に近い順で届くが、厳密な順序保証はない) |
| マルチリージョン | 1 リージョンの単一ストリームで全リージョンの DSQL コミット済み書き込みをキャプチャ |
| 性能影響 | データベースワークロードのパフォーマンスへの影響なし |
CDC ストリーム自体はフルマネージドで、変更イベントのキャプチャに必要なインフラは Aurora DSQL 側が管理します。ユーザーは配信先の Kinesis データストリームと、Aurora DSQL が書き込みに使用する IAM ロールを自分のアカウントに用意します。
ユースケースとしては以下が挙げられています。
- ダウンストリームシステムの同期: 検索インデックス、キャッシュ、データウェアハウス、分析システムへの変更のレプリケーション。
- イベント駆動アーキテクチャ: データベース変更をトリガーにしたワークフロー、通知、マイクロサービスの起動。
- 監査証跡: コンプライアンス、デバッグ、履歴分析のためのトランザクション記録。
- プロデューサーとコンシューマーの疎結合化: データベースはトランザクションに専念し、ダウンストリームは自分のペースで変更を処理。CQRS への応用など。
Kinesis Data Streams に届いた後は、AWS Lambda のイベントソースにしたり、Amazon Data Firehose 経由で Amazon S3・Amazon Redshift・Amazon OpenSearch Service へ配信したりできます。
プレビューからの変更点
GA に伴う最大の変更点は、CDC レコードの op フィールドで UPDATE が区別されるようになったことです。
| 項目 | プレビュー時点 | GA 時点 |
|---|---|---|
INSERT の op |
"c" |
"c" |
UPDATE の op |
"c"(INSERT と区別不可) |
"u" |
DELETE の op |
"d" |
"d" |
UPDATE が op: "u" で区別されるようになりました
プレビュー時点では INSERT と UPDATE がどちらも op: "c" として配信されており、ダウンストリーム側で区別できませんでした。GA では予告通り op: "u" が追加され、行の変更種別を判別できるようになりました。
UPDATE のレコード例は以下のようになります。
{
"type": "full",
"op": "u",
"before": null,
"after": {"order_id": 1001, "item_id": 42, "quantity": 10, "price": "29.99"},
"source": {
"version": "1.0",
"ts_ms": 1705318300000,
"ts_ns": 1705318300000000000,
"txId": "qvtiesgmd55cvlfukm3dfuotji",
"schema": "public",
"table": "order_items",
"db": "postgres",
"cluster": "kmabugltfmjdaj2siqr2qbxgju"
},
"ts_ms": 1705318300125,
"ts_ns": 1705318300125483291
}
after フィールドには変更後の行の全カラムが含まれます。UPDATE でも変更前の行イメージ(before image)は含まれず before は null である点は変わりません。なお DELETE の場合は、主キーを持つテーブルであれば before フィールドに削除された行の主キー値が入ります。
Write-set compaction(書き込みセットの圧縮)
GA 版のドキュメントでは、トランザクション内の複数操作をどう 1 レコードにまとめるかが「write-set compaction」として明文化されました。Aurora DSQL はトランザクションのコミット時に各行の前後の状態を比較し、正味の変化(net effect)を 1 行につき最大 1 レコードだけ発行します。
| トランザクション前の行 | トランザクション後の行 | 発行されるレコード |
|---|---|---|
| 存在しない | 存在する | op: "c"(after は最終状態) |
| 存在する | 存在する | op: "u"(after は最終状態) |
| 存在する | 存在しない | op: "d"(before は主キー値のみ) |
| 存在しない | 存在しない | レコードなし |
例えば同一トランザクション内で INSERT した行を UPDATE した場合は op: "c" が 1 レコードだけ発行され、INSERT した行を DELETE した場合は正味の変化がないためレコード自体が発行されません。監査ログのように SQL ステートメント単位の記録が必要な場合は、ステートメントごとにトランザクションを分ける必要があります。
注意点
GA 時点でも以下の点には注意が必要です。
- 配信保証は at-least-once: 同じレコードが複数回配信される可能性があります。
source.ts_ns(ナノ秒精度のコミットタイムスタンプ)と主キー値の組み合わせで重複を検出できます - 順序は UNORDERED: 概ねコミット順に届きますが厳密な保証はありません。コミット順が必要な場合は受信側で
source.ts_nsを使って並べ替えます。同一トランザクションのレコードのグループ化にはsource.txIdを使います - 主キーの定義が必須: 主キーがないテーブルでは、ダウンストリームで重複排除や削除対象行の特定ができません。CDC 対象のテーブルには必ず主キーを定義しましょう
- Kinesis 側の設定:
MaxRecordSizeInKiBはデフォルトの 1 MiB ではなく 10240(10 MiB)を設定します。設定した上限を超えるレコードが発生すると CDC ストリームがKINESIS_OVERSIZE_RECORDで impaired 状態になります - 9 MiB 超のレコードは分割: シリアライズ後の JSON が 9 MiB を超えると
chunked/fragmentレコードに分割されるため、受信側で再組み立てが必要です - ストリーム数の上限: 1 クラスターあたりの CDC ストリーム数には上限があります。上限に達すると
CreateStreamがServiceQuotaExceededExceptionを返します。既定値はクォータのドキュメントをご確認ください
料金
CDC の料金は、キャプチャしたデータ量に基づいて StreamDPU として計測されます。StreamDPU は他の DPU サブコンポーネントと同じレートで課金されます。
| 項目 | 内容 |
|---|---|
| CDC の課金単位 | StreamDPU(通常の DPU と同じレート) |
| DPU 単価 | $10.00 / 100 万 DPU(アジアパシフィック (東京) の場合) |
| 変更がない場合 | Stream DPU の課金なし |
| Kinesis Data Streams | 標準料金が別途発生 |
| 無料利用枠 | 月あたり 100,000 DPU + 1 GB 月 のストレージ |
ストリーミングする変更がなければ Stream DPU の課金は発生しないため、CDC ストリームを作成しておくこと自体のコストは気にしなくてよさそうです。Kinesis Data Streams および Lambda や Firehose などダウンストリームのサービスの料金は別途発生する点にご注意ください。
最新の料金はAurora DSQL の料金ページをご確認ください。
やってみる
GA 版の CDC ストリームで、op フィールドが c / u / d で区別されることと、write-set compaction の動作を確認してみます。
前提条件は以下の通りです。
- Aurora DSQL クラスターが
ACTIVE状態であること(アイドル状態の場合は PostgreSQL クライアントで接続して起こしておきます。ACTIVEでないとCreateStreamがバリデーションエラーになります)。 - Aurora DSQL クラスター、Kinesis データストリーム、IAM ロール、呼び出し元プリンシパルがすべて同一の AWS アカウント・同一リージョンにあること。
- CLI は CloudShell 上で実行。
CDC ストリームのセットアップ
セットアップ手順はプレビュー時と同じです。詳細な IAM ポリシーの内容などはプレビュー時のエントリをご覧ください。ここでは要点のみ再掲します。
まず配信先の Kinesis データストリームを作成します。レコードサイズ上限(MaxRecordSizeInKiB)は 10240(10 MiB)、キャパシティモードはオンデマンドが推奨です。
$ aws kinesis create-stream \
--stream-name my-cdc-stream \
--stream-mode-details StreamMode=ON_DEMAND \
--max-record-size-in-ki-b 10240 \
--region ap-northeast-1
次に Aurora DSQL が Kinesis に書き込むための IAM ロールを作成します。信頼ポリシーで dsql.amazonaws.com を Principal とし、混乱した代理(confused deputy)対策として aws:SourceAccount と aws:SourceArn の条件を設定します。権限ポリシーには kinesis:PutRecord / kinesis:PutRecords / kinesis:DescribeStreamSummary / kinesis:ListShards を付与します(KMS のカスタマーマネージドキーを使う場合は kms:GenerateDataKey も必要です)。
CDC ストリームを作成します。
$ aws dsql create-stream \
--cluster-identifier CLUSTER_ID \
--target-definition '{"kinesis":{"streamArn":"arn:aws:kinesis:ap-northeast-1:123456789012:stream/my-cdc-stream","roleArn":"arn:aws:iam::123456789012:role/dsql-cdc-role"}}' \
--ordering UNORDERED \
--format JSON \
--region ap-northeast-1
{
"clusterIdentifier": "CLUSTER_ID",
"streamIdentifier": "STREAM_ID",
"arn": "arn:aws:dsql:ap-northeast-1:123456789012:cluster/CLUSTER_ID/stream/STREAM_ID",
"status": "CREATING",
"creationTime": "2026-07-09T02:47:15.854000+00:00",
"ordering": "UNORDERED",
"format": "JSON"
}
ストリームの作成には 1〜3 分程度かかります。get-stream で状態が ACTIVE になったことを確認します。
$ aws dsql get-stream \
--cluster-identifier CLUSTER_ID \
--stream-identifier STREAM_ID \
--region ap-northeast-1 \
--query 'status'
"ACTIVE"
INSERT / UPDATE / DELETE で op を確認する
テスト用のテーブルを作成して、INSERT・UPDATE・DELETE をそれぞれ別のトランザクション(オートコミット)で実行します。CDC で重複排除や削除の相関を行うために、テーブルには主キーを必ず定義します。
CREATE TABLE test_cdc (
id INT PRIMARY KEY,
message TEXT
);
INSERT INTO test_cdc VALUES (1, 'hello cdc');
UPDATE test_cdc SET message = 'hello ga' WHERE id = 1;
DELETE FROM test_cdc WHERE id = 1;
Kinesis データストリームからレコードを読み取ります。Aurora DSQL はランダムなパーティションキーでレコードをシャードに分散させるため、全シャードを読み取る必要があります。
$ for SHARD_ID in $(aws kinesis list-shards \
--stream-name my-cdc-stream \
--region ap-northeast-1 \
--query 'Shards[].ShardId' --output text); do
SHARD_ITERATOR=$(aws kinesis get-shard-iterator \
--stream-name my-cdc-stream \
--shard-id "$SHARD_ID" \
--shard-iterator-type TRIM_HORIZON \
--region ap-northeast-1 \
--query 'ShardIterator' --output text)
aws kinesis get-records \
--shard-iterator "$SHARD_ITERATOR" \
--region ap-northeast-1 \
--query 'Records[].Data' --output text
done
取得した Data フィールド(Base64 エンコード)をデコードすると、3 つの操作に対応するレコードが確認できます。
INSERT は op: "c" で配信されます。
{
"type": "full",
"op": "c",
"before": null,
"after": {
"id": 1,
"message": "hello cdc"
},
"source": {
"version": "1.0",
"ts_ms": 1783565823773,
"ts_ns": 1783565823773460473,
"txId": "svt5cm5lx2xbhzbn77gzxmp7ci",
"schema": "public",
"table": "test_cdc",
"db": "postgres",
"cluster": "CLUSTER_ID"
},
"ts_ms": 1783565823772,
"ts_ns": 1783565823772881451
}
UPDATE は op: "u" で配信されます。プレビュー時は INSERT と同じ "c" だったので、確かに区別されるようになりました!
{
"type": "full",
"op": "u",
"before": null,
"after": {
"id": 1,
"message": "hello ga"
},
"source": {
"version": "1.0",
"ts_ms": 1783565823840,
"ts_ns": 1783565823840086440,
"txId": "xbt5cm5l2fkdumfhdg6w77rzom",
"schema": "public",
"table": "test_cdc",
"db": "postgres",
"cluster": "CLUSTER_ID"
},
"ts_ms": 1783565823839,
"ts_ns": 1783565823839891247
}
DELETE は op: "d" で、before フィールドに主キー値が入ります。
{
"type": "full",
"op": "d",
"before": {
"id": 1
},
"after": null,
"source": {
"version": "1.0",
"ts_ms": 1783565823880,
"ts_ns": 1783565823880754862,
"txId": "yft5cm5l3rkt2k5fa25tjalc6i",
"schema": "public",
"table": "test_cdc",
"db": "postgres",
"cluster": "CLUSTER_ID"
},
"ts_ms": 1783565823880,
"ts_ns": 1783565823880136762
}
Write-set compaction を確認する
次に、同一トランザクション内の複数操作が 1 レコードに圧縮されることを確認します。INSERT した行を同一トランザクション内で UPDATE してからコミットします。
BEGIN;
INSERT INTO test_cdc VALUES (2, 'first');
UPDATE test_cdc SET message = 'compacted' WHERE id = 2;
COMMIT;
配信されるのは op: "c" の 1 レコードのみで、after には UPDATE 後の最終状態が入ります。
{
"type": "full",
"op": "c",
"before": null,
"after": {
"id": 2,
"message": "compacted"
},
"source": {
"version": "1.0",
"ts_ms": 1783566449655,
"ts_ns": 1783566449655623084,
"txId": "mvt5cnqowtxtj75hju7kwhwa7m",
"schema": "public",
"table": "test_cdc",
"db": "postgres",
"cluster": "CLUSTER_ID"
},
"ts_ms": 1783566449566,
"ts_ns": 1783566449566168084
}
さらに、INSERT した行を同一トランザクション内で DELETE した場合はどうなるでしょうか。
BEGIN;
INSERT INTO test_cdc VALUES (3, 'ephemeral');
DELETE FROM test_cdc WHERE id = 3;
COMMIT;
行の正味の変化がゼロのため、id = 3 に関するレコードは配信されません。ドキュメントの記載通り、CDC はステートメント単位ではなくトランザクションの正味の結果を配信することが確認できます。
さいごに
プレビュー発表から約 2 ヶ月で GA になりました。プレビュー時に最大の注意点だったINSERT と UPDATE がどちらも op: "c" になるという問題が予告通り解消され、write-set compaction の動作も明文化されたことで、ダウンストリーム側の処理を安心して設計できるようになりました。
Aurora DSQL はサーバーレスで強い一貫性を持つ分散 SQL データベースですが、CDC の GA によってイベント駆動アーキテクチャの起点としても本番採用できるようになりました。at-least-once かつ UNORDERED という特性は GA でも変わらないため、source.ts_ns と主キーによる重複排除・順序付けをダウンストリーム側に組み込むことだけ押さえておけば、検索インデックスの同期や監査証跡といった定番ユースケースにドンドン使って行きたいです。







