データベース

チェンジデータフィードとは?Delta LakeのCDF有効化と読み取り実装・採用判断

チェンジデータフィード(Change Data Feed/CDF)は、Delta Lakeのテーブルに対して「バージョン間で行がどう変わったか」を行単位で読み出せるようにするテーブル機能です。スナップショットを時点指定で取り出すタイムトラベルと違い、挿入・更新・削除の一件ずつに種別のラベルが付いた形で差分が返る仕組みです。データベースの変更を外部へ捕捉するCDCと発想は近いものの、こちらはテーブルフォーマット側の機能なので、コネクタを立てずにSQLとDataFrameだけで差分パイプラインを組めます。この記事では、有効化のテーブルプロパティ、返る3つのメタデータ列、readChangeFeedによる読み取り、そしてVACUUMで変更データファイルが消えるという保持の壁までを、実装者の目線で順に整理します。

まとめ|チェンジデータフィード採用の可否を分ける3つの前提条件

CDFが噛み合うのは、次の3つが同時に成り立つ場合です。第一に、差分の発生元がDelta Lakeのテーブルで、そこから下流へ伝播させたい処理があること。第二に、テーブルを作った時点、あるいは差分を使い始める前の時点で有効化を済ませられること。第三に、変更データファイルが保持ポリシーで消える前に、下流の処理を回し切れる運用体制があること。2つ目を後回しにすると、有効化より前の変更は一切読めないため、初回だけ全件で埋め直す工程が別途必要になります。

実装で最初に決めるのは2点です。更新の「変更前の像」を下流で使うかどうか、そして読み取りをバッチとストリーミングのどちらで回すか。前者でupdate_preimageを捨てられるかが決まり、後者で範囲指定の作法とチェックポイントの持ち方が決まります。この2点が曖昧なまま組むと、差分の突き合わせロジックを後から書き直すことになります。

見送りが妥当なのは、対象テーブルの更新と削除がほとんど発生しない場合、非加法的なスキーマ変更が頻繁に入る場合、そして変更元がDelta Lakeの外にある場合です。1つ目は差分を読む仕掛けを足す価値が薄く、2つ目は読み取り自体が範囲をまたげずに詰まり、3つ目はCDCの領分になります。判断の分岐は第5章と第6章に条件付きで示しました。

行単位の変更履歴をテーブル機能として配るCDFの定義と適用範囲

用語の混乱が起きやすいのは、タイムトラベルとCDCの2つとの関係です。先に切り分けておくと、公式ドキュメントの読み方が定まります。

タイムトラベルとの違いは差分そのものが行単位で返るかどうかにある

Delta Lakeにはバージョン指定またはタイムスタンプ指定で過去のスナップショットを読む機能があり、これはDelta Lakeの基本構造とトランザクションログの側で成り立っています。ただしスナップショットは「その時点のテーブル全体」であって、2つのバージョンの差分ではありません。差分が欲しければ、旧バージョンと新バージョンを両方読んで結合し、行ごとに突き合わせる処理を自分で書くことになります。

CDFはこの突き合わせをテーブル側が肩代わりする機能です。読み取り時に範囲を渡すと、その範囲で起きた変更だけが行として返り、各行には種別のラベルが付きます。テーブルが10億行あっても、その範囲で3万行しか変わっていなければ読むのは3万行です。差分パイプラインの計算量が全体量ではなく変更量に比例する、という一点がCDFを入れる理由になります。

CDCやDB組み込みの変更配信と役割が分かれる境界線の見極め方

CDCは変更を捕捉する方式の総称で、クエリベース・トリガーベース・ログベースの3系統があります。捕捉の対象はたいていアプリケーションが書き込む業務データベースで、捕捉した変更をデータ基盤側へ運ぶところまでが役目です。Cosmos DBやDynamoDBがDB組み込み機能として配る変更フィードも、立ち位置はこちら側になります。

CDFの守備範囲はその先です。データ基盤に着いた後、ブロンズ層からシルバー層、シルバー層からゴールド層へと差分を送る内側の伝播を担います。つまり2つは競合せず、直列に並びます。業務DBからレイクまではCDC、レイクの中はCDF、という分業が現場での既定の形だと考えてください。

_change_typeと2つのメタデータ列が返す情報の読み方

CDFを有効にしたテーブルを差分読み取りすると、通常のデータ列に加えて3つのメタデータ列が付きます。この3列の意味を先に押さえておくと、下流の処理の書き方が決まります。

列名 入る値と意味
_change_type String insert/update/deleteの4値
_commit_version Long 変更を含むDeltaログのバージョン
_commit_timestamp Timestamp コミットが作成された時刻

注意すべきは_change_typeの値が4種類ある点です。1回の更新はupdate_preimageupdate_postimageの2行として返り、前者が更新前、後者が更新後の内容を持ちます。下流へ最新値だけを送りたい場合、update_preimageは基本的に捨てる対象です。逆に監査ログのように「何が何に変わったか」を残したい場合は、_commit_versionでペアを組んで両方を保存します。この使い分けを決めないまま読み始めると、更新1件が2行に増えたまま下流へ流れ、件数が合わなくなります。

enableChangeDataFeedによる有効化と変更が記録されない条件

有効化はテーブルプロパティを1つ立てるだけで済みます。ただし、いつ立てるかと、何が記録されないかの2点に落とし穴があります。

新規テーブルと既存テーブルで書き方が分かれる有効化の設定手順

使うプロパティはdelta.enableChangeDataFeedで、値をtrueにします。新規テーブルならCREATE TABLETBLPROPERTIESで指定し、既存テーブルではALTER TABLEによる後設定が必要です。プロジェクト全体で既定にしたい場合は、Sparkの設定spark.databricks.delta.properties.defaults.enableChangeDataFeedを立てておくと、以降に作られるテーブルへ自動で入ります。

対象 指定する場所 効き始める範囲
新規テーブル CREATE TABLEのプロパティ 最初のコミットから
既存テーブル ALTER TABLE SET 設定した後の変更から
全新規テーブル Sparkのdefaults設定 設定後に作るテーブル

3つのうち実務で効いてくるのは3行目です。データ基盤の中で差分伝播を前提にするなら、テーブルごとに付け忘れる余地を残すより、セッションの既定に寄せてしまうほうが漏れません。Databricksでワークスペースを組んでいる場合は、クラスタのSpark設定に入れておくと、ノートブックから作られたテーブルにも同じ既定が乗ります。

有効化より前に発生した変更は一切読めないという前提の扱い方と段取り

記録されるのは有効化した後に発生した変更だけです。既存の巨大なテーブルへ後からALTER TABLEを打った場合、その時点より過去の変更履歴はCDFとして読めません。トランザクションログに残っている範囲であればタイムトラベルで旧スナップショットは取れますが、それはCDFの読み取りとは別の経路になります。

移行案件でこの前提を外すと事故になります。段取りとしては、まず対象テーブルでCDFを有効化し、有効化が完了したバージョン番号を控え、そのバージョンを基準に下流を全件で1回埋めてから、以降を差分へ切り替える流れです。順序を逆にして「全件を埋めてから有効化」にすると、埋め込み中に入った更新が差分にも全件にも入らず、その分だけ下流がずれます。控えたバージョン番号は、後述するstartingVersionの初期値としてそのまま使えます。

挿入のみと全パーティション削除で変更データファイルが増えない仕組み

CDFを有効にすると、変更内容が_change_dataというディレクトリへ別ファイルとして書かれる場合があります。ただし全ての操作でファイルが増えるわけではありません。公式ドキュメントには、挿入のみの操作と全パーティション削除については_change_dataにデータを生成せず、既存のParquetファイルから変更を導出すると明記されています。

この挙動から2つ読み取れます。1つは、追記中心のテーブルならCDFを有効にしてもストレージの増分がほぼ発生しないこと。もう1つは、行単位の更新や削除が多いテーブルほど追加のファイルが積み上がることです。費用の見積もりは、テーブルの総量ではなく更新と削除の発生量から立ててください。なお公式ドキュメントは、この変更データファイルを直接クエリしてフィードを再構成することを避けるよう明記しています。ディレクトリの内部構造は実装の詳細であり、後方互換の保証がない領域だと考えるのが安全です。

readChangeFeedでバッチとストリーミングの差分を読み取る実装

読み取り側は、オプションを1つ足して範囲を渡すだけの構造です。バッチとストリーミングで書き方が共通している点が、切り替えを楽にします。

startingVersionと時刻指定で範囲を切るバッチ読み取りの書き方

読み取りの入口はreadChangeFeedオプションで、値をtrueにします。範囲の指定はバージョン番号か時刻のどちらかで、startingVersionendingVersion、またはstartingTimestampendingTimestampを渡します。終端を省くと、指定した開始点から現在までが読み取り対象です。

オプション 指定する内容 省略時の挙動
readChangeFeed true固定 通常のスナップショット読み取り
startingVersion 開始バージョン番号 時刻指定を使う
endingVersion 終了バージョン番号 現在まで読む
startingTimestamp 開始時刻 バージョン指定を使う

運用ではバージョン指定を軸に組むほうが安全です。時刻指定はコミットの時刻に依存するため、書き込みが遅延したバッチがあると境界の解釈がぶれます。前回どこまで読んだかを_commit_versionの最大値として保存し、次回はその値を開始点に渡す形にすれば、境界の判定がテーブル側の番号だけで閉じます。

更新前後のペアを畳んで下流へ最新値だけを渡す実装手順と注意点

差分を読んだ後の処理は、下流が求める形で2通りに分かれます。最新値だけを反映したいなら、まず_change_typeupdate_preimageの行を落とす処理が先です。そのうえで主キーごとに_commit_versionの最大値を持つ行だけを残し、種別がdeleteなら削除、それ以外なら挿入または更新としてマージします。同一キーが範囲内で複数回変わっている場合に、この畳み込みを飛ばすと反映順が崩れます。

マージ先への書き込みは、主キーによるMERGE INTOに寄せてください。同じ範囲を2回読んでも結果が変わらないため、再実行時の巻き戻しが要らなくなります。監査用途で履歴を残す場合は逆に、畳み込みをせずupdate_preimageごと追記専用のテーブルへ入れ、_commit_timestampで並べ替えられる形にしておきます。

ストリーミング読み取りで押さえるチェックポイントと再開の作法

readStream側でも同じreadChangeFeedオプションが使えます。開始点としてstartingVersionを渡し、以降の進捗はチェックポイントに残る形です。

再開で気をつける点は主に2つです。1つはチェックポイントを消したときで、開始点の指定がそのまま効くため、消す前に現在のバージョン番号を控えておかないと最初から読み直すことになります。もう1つは長時間の停止で、後述する保持の壁を越えると必要な変更データファイルが消えている可能性があります。ストリーム処理としての遅延と再処理の考え方はここでも同じで、停止許容時間を保持期間から逆算してアラートの閾値に落としてください。

VACUUMと保持ポリシーが読み取り可能範囲を決める運用設計

CDFで最も見落とされるのが保持の扱いです。差分が永久に読めるわけではないという一点を、設計の早い段階で数字にしておく必要があります。

変更データファイルがVACUUMの削除対象になる前提での保持設計

Databricksの公式ドキュメントには、レガシー方式の変更データファイルはテーブルの保持ポリシーに従い、VACUUMコマンドが削除すると明記されています。トランザクションログ側の変更はチェックポイントの保持ポリシーに従うため、2つの保持設定が両方とも読み取り可能範囲に効いてきます。

設計に落とすと、決めるべきは1つです。下流が止まったときに何日分まで取り戻せる必要があるか。その日数より保持を短くすると、復旧時に差分が欠けます。逆に必要以上に長く保つと、消えないファイルの分だけストレージ費用が積み上がります。実務では下流の停止許容時間の3倍程度を目安に置き、監視で滞留を検知したら手動の再実行が間に合う設計にしておくのが無理のない線です。

レガシー方式と自動方式で分かれるストレージ費用の見積もり手順

Databricksのドキュメントは、変更が別ファイルに記録されうるレガシー方式についてストレージ費用が小幅に増えると説明し、自動方式についてはレガシー方式と比べて書き込み性能が上がりストレージ費用が下がると説明しています。2026年8月時点での記述であり、方式の呼び分けと既定の扱いは製品側の更新で変わりうるため、費用を試算する前にワークスペースのドキュメントで現行の既定を確認してください。

見積もりの手順は方式によらず同じです。対象テーブルの更新行数と削除行数を1日分実測し、1行あたりのバイト数を掛けて日次の増分を出します。そこへ保持日数を掛ければ、定常状態で持ち続けるおおよその容量が出ます。

スキーマ変更とテーブル機能の制約で読み取りが止まる3つの条件

読み取りを止める制約が3種類あります。第一に、列の削除・名前変更・型変更といった非加法的なスキーマ変更を含む範囲は読み取れません。第二に、行フィルターと列マスクを設定したテーブルは対応外です。第三に、外部のIcebergクライアントから自動方式のフィードを照会することはできません。

カラムマッピングを併用する場合は、Delta Lake側のバージョンで対応範囲が変わります。整理すると次のとおりです。

Delta Lakeのバージョン カラムマッピング併用時の対応
2.0以前 CDFの読み取りは非サポート
2.1 非加法的変更が無い範囲のバッチのみ
2.2 同条件でバッチとストリーミング
2.3 非加法的変更を含む範囲もバッチ可
3.0以上 スキーマ追跡でストリーミング可

本体の最新安定版は4.3系(PyPIのdelta-sparkで実測・4.3.1が2026年7月8日公開)なので、新規に組むなら表の最終行の条件が前提になります。ただしランタイム側が固定されている案件では古い系列が動いていることがあるため、着手時にテーブルのプロトコルバージョンとランタイムの組み合わせを確認してください。列名の変更をリリースに含める予定があるなら、その前後で差分パイプラインを一度止め、変更後のバージョンを新しい開始点として仕切り直す段取りを組み込んでおくと、読み取り不能で止まる事故を避けられます。

受託開発の現場でチェンジデータフィードを採用しない場面と代替の設計

ここまでの制約を、実際の案件でどう判断へ落とすかを条件付きで示します。

差分伝播とテーブル全再計算のどちらが軽いかを測るための2つの基準

差分で回すか、下流を毎回作り直すかは、変更率とテーブル規模の2軸が判断材料です。目安として、1回の実行で変わる行が全体の10パーセントを超えるなら、差分の読み取りと畳み込みとマージを積み上げるより、対象範囲を全件で作り直すほうが処理時間も実装量も軽く収まります。逆に、数億行のテーブルで日次の変更が数万行に留まるなら、差分に寄せる価値がはっきり出ます。

層ごとに答えが変わる点も押さえてください。ブロンズ・シルバー・ゴールドの層設計で言えば、ブロンズからシルバーは追記が中心で差分が効きやすく、ゴールド層の集計テーブルは元の粒度が集約で潰れるため、差分をそのまま足し引きできないことが多くなります。集計の再計算が必要な場合は、変更のあったキーの集合だけをCDFで取り出し、そのキーに限って集計をやり直す形にすると中間の重さを避けられます。

見送りが妥当になる3つの条件と代わりに選ぶ構成の判断基準と検証手順

見送るべき条件を挙げます。第一に、対象テーブルが追記のみで更新と削除がほぼ発生しない場合。この要件なら、バージョン範囲を指定した通常の増分読み取りで足り、メタデータ列を扱う分だけ実装が増えます。第二に、非加法的なスキーマ変更が繰り返し入る場合。読み取りが範囲をまたげず、そのたびに仕切り直しの運用が発生するため、割に合いません。第三に、変更元がDelta Lakeの外にある場合です。業務DBから拾いたいのであれば、それはCDCの領分であってCDFでは届きません。

代替は要件で決まります。追記のみならバージョン指定の増分読み取り、業務DBが起点ならCDCツール、レイク内部の差分伝播ならCDFという順で当てはめてください。検証は、本番相当の更新量で2週間流し、変更データファイルの増分と下流の遅延を実測するところから始めるのが確実です。層の切り分けと保持設計まで含めて外部と組む選択肢もあり、当社ではデータ分析基盤構築・MLOps構築支援として、この見極めの工程から支援しています。

よくある質問

チェンジデータフィードの検討で実際に挙がる質問のうち、公式ドキュメントの記述に照らして答えられるものを5つ挙げます。

チェンジデータフィードはタイムトラベルと何が違いますか?

返るものが違います。タイムトラベルは指定したバージョンや時刻におけるテーブル全体のスナップショットを返すのに対し、チェンジデータフィードは指定した範囲で発生した変更を行単位で返します。

有効化する前の変更履歴も読めますか?

読めません。記録されるのはdelta.enableChangeDataFeedを有効にした後に発生した変更だけで、それ以前の履歴はフィードに現れない仕様です。既存テーブルへ後から入れる場合は、有効化した時点のバージョン番号を控え、そのバージョンを基準に下流を1回全件で埋めてから差分へ切り替える段取りにしてください。

更新1件がなぜ2行として返るのですか?

_change_typeupdate_preimageupdate_postimageという2つの値があり、更新は更新前の行と更新後の行のペアで返るためです。下流へ最新値だけを渡すならupdate_preimageの行を落とし、変更内容そのものを監査ログとして残すなら_commit_versionでペアを組んで両方を保存します。

CDFを有効にするとストレージ費用はどれくらい増えますか?

更新と削除の発生量に比例します。公式ドキュメントによれば、挿入のみの操作と全パーティション削除は変更データのファイルを生成せず既存のParquetファイルから導出するため、追記中心のテーブルなら増分はごく小さく収まります。行単位の更新や削除が多いテーブルでは追加のファイルが積み上がるので、更新行数の実測値に保持日数を掛けて試算してください。

変更データフィードのデータは消えることがありますか?

消えます。Databricksのドキュメントには、レガシー方式の変更データファイルがテーブルの保持ポリシーに従いVACUUMコマンドの削除対象になると明記されています。下流の処理が長期間止まると、再開時に必要な範囲のファイルが残っていない事態が起こりうるため、停止許容時間から保持日数を逆算し、滞留を検知するアラートを併せて設けてください。

関連記事

資料請求

RELATED POSTS 関連記事