What you will be able to do
- Explain what a change data feed record contains and why it supports incremental processing
- Tell legacy change data feed (delta.enableChangeDataFeed) apart from automatic change data feed, and enable or migrate between them
- Read the _change_type, _commit_version and _commit_timestamp metadata columns, including update pre- and post-images and deletes
- Run bounded batch reads of changes with table_changes() and the readChangeFeed option
Key concept
Change Data Feed (CDF) — A row-level log of what changed between versions of a Delta table. Each record holds the row's values plus metadata saying whether that row was inserted, updated (before and after images) or deleted. Downstream jobs can then process only those changed rows instead of rescanning the whole table.
1.Why a table needs to expose its own changes
Take a 500-million-row dimension table where a few thousand rows change each day. Downstream consumers such as a silver-to-gold job, a replica or an audit table only care about those few thousand rows. Without a change log they have two choices: rescan the whole table, or compare two snapshots to work out what was inserted, updated or deleted. Both cost far more than the change itself.
Change Data Capture (CDC) solves this for operational databases. Instead of sending entire datasets, it produces a feed of only the modified records. Delta tables have their own version of this, called the Change Data Feed (CDF). The table records what happened to each row at each version, so a consumer can ask for "everything that changed since version N" and get back only those rows.
| Use case | What the feed provides |
|---|---|
| Incremental ETL pipelines | Only the rows that changed since the last pipeline run |
| Audit trails | A record of data modifications for compliance and governance |
| Data replication | Changes to sync into downstream tables, caches, or external systems |
Checkpoint 1 of 6· Check yourself
A source offers only periodic snapshots and no change feed. How can a consumer find out which rows were inserted, updated or deleted?
Snapshots hold full table state, not changes, so the changes have to be inferred by comparing them. A change feed avoids that work by recording the changes directly.
“Because snapshots don't capture record-level changes, identifying what changed requires comparing records across snapshots to infer inserts, updates, and deletes.”Source: docs.databricks.com
2.Turning it on: legacy vs automatic change data feed
Databricks now offers two ways to get a change feed, and the exam guide's property name, delta.enableChangeDataFeed, belongs to the older one.
Legacy change data feed writes the changes out at write time. It works only on Delta Lake tables, and you enable it per table with a table property, either at creation or later with ALTER TABLE ... SET TBLPROPERTIES. Because it materialises changes on write, storage costs can rise slightly, since changes might be recorded in separate files. Some operations, such as insert-only writes or full-partition deletes, produce no change data files at all. Databricks computes their changes straight from the transaction log instead.
Automatic change data feed works out row-level changes at query time instead. It uses row tracking on Delta tables (row lineage on Iceberg v3). You don't configure it per table: any table that meets the requirements supports it automatically. The requirements are Databricks Runtime 19 or above and a Unity Catalog table, either a managed Delta table with row tracking, an Iceberg v3 table, or an external Delta table with row tracking. MERGE INTO and UPDATE no longer pay a change-computation cost on every write, which improves write performance and reduces storage. Both approaches share the same read APIs, table_changes() and readChangeFeed. For Delta tables, only Databricks readers can query the automatic feed.
ALTER TABLE myDeltaTable
SET TBLPROPERTIES (delta.enableChangeDataFeed = true)> CREATE TABLE myschema.t(c1 INT, c2 STRING) TBLPROPERTIES(delta.enableChangeDataFeed=true);To migrate a Delta table from legacy to automatic, first check that the table meets the requirements, then remove the legacy property. You can't run both feeds on one table. Like any table-property change, setting or unsetting this property conflicts with concurrent writes, so do it when nothing else is writing to the table.
ALTER TABLE <table_name> UNSET TBLPROPERTIES ('delta.enableChangeDataFeed');Checkpoint 2 of 6· Check yourself
A Unity Catalog managed Delta table with row tracking has delta.enableChangeDataFeed = true. The team wants automatic change data feed for better MERGE performance. What must they do after confirming the table meets the requirements?
The two feeds can't be active together. Migrating means unsetting the legacy property so the automatic feed takes over.
“You can't use both legacy and automatic change data feeds together.”Source: docs.databricks.com
3.What a change row looks like: updates and deletes exposed
A change feed read returns every data column of the table, using the schema of the latest table version, plus three metadata columns. These columns turn a plain set of rows into an ordered list of events. Inserts and deletes each produce one row. An update produces two: the preimage holds the values before the update and the postimage holds the values after it. That pair is what lets a downstream job tell what changed as well as what the row is now.
A table can't use change data feed if it already has columns with these names. Rename them before you enable the feed.
| Column | Type | Meaning |
|---|---|---|
| _change_type | STRING | insert, update_preimage, update_postimage, or delete |
| _commit_version | BIGINT (Long) | The table version containing the change |
| _commit_timestamp | TIMESTAMP | When the commit was created |
Here is the worked example from the table_changes reference. A CDF-enabled table gets two inserts, an UPDATE on two rows and a DELETE of one row:
-- Modify the table
> INSERT INTO myschema.t VALUES (1, 'Hello'), (2, 'World');
> INSERT INTO myschema.t VALUES (3, '!');
> UPDATE myschema.t SET c2 = upper(c2) WHERE c1 < 3;
> DELETE FROM myschema.t WHERE c1 = 3;> SELECT * FROM table_changes('`myschema`.`t`', 2);
c1 c2 _change_type _commit_version _commit_timestamp
3 ! insert 2 2022-09-01T18:32:28.000+0000
2 WORLD update_postimage 3 2022-09-01T18:32:32.000+0000
2 World update_preimage 3 2022-09-01T18:32:32.000+0000
1 Hello update_preimage 3 2022-09-01T18:32:32.000+0000
1 HELLO update_postimage 3 2022-09-01T18:32:32.000+0000
3 ! delete 4 2022-09-01T18:32:35.000+0000Checkpoint 3 of 6· Match them up
Match each _change_type value to the event that produces it
Tap a term, then the definition that fits it.
Updates are split into before and after images. Inserts and deletes each produce a single row.
“preimage is the value before the update, postimage is the value after the update.”Source: docs.databricks.com
Sources1
4.Reading a range of changes in batch
For a one-off or scheduled batch read, you give a starting point and, optionally, an ending point. Each can be a version number or a timestamp. In SQL the entry point is the table-valued function table_changes(table_str, start [, end]). If you leave out end, you get every change from start up to the current version. To call it you need SELECT on the table, ownership of the table, or admin privileges.
Rules that come up on the exam:
- A start is mandatory for batch reads.
- Both bounds are inclusive. table_changes('t', 0, 10) includes versions 0 and 10.
- You can't go back before enablement. A start version from before change data feed was enabled raises an error.
- Timestamps are strings in the format yyyy-MM-dd[ HH:mm:ss[.SSS]], and versions are integers.
SELECT * FROM table_changes('tableName', 0, 10)spark.read \
.option("readChangeFeed", "true") \
.option("startingVersion", 0) \
.option("endingVersion", 10) \
.table("myDeltaTable")Checkpoint 4 of 6· Fill the gap
Which option turns this ordinary batch read into a change feed read?
spark.read \
.option(" ? ", "true") \
.option("startingVersion", 0) \
.table("<table_name>")readChangeFeed is the reader option. delta.enableChangeDataFeed is the table property that turns on legacy CDF, and skipChangeCommits and ignoreChanges are streaming options for ignoring changes.
Source: docs.databricks.comCheckpoint 5 of 6· Check yourself
A job last processed changes up to and including version 41. It now runs SELECT * FROM table_changes('sales', 41, 45). What is wrong?
Both bounds are inclusive, so the next run should start at 42. table_changes accepts versions or timestamps.
“Start and end versions are inclusive.”Source: docs.databricks.com
Checkpoint 6 of 6· Exam question
A platform team is documenting how Change Data Feed can be enabled and what happens if it is toggled off and back on for a Delta table. Which of the following statements are correct?(Select 3)
Correct answers: A, B, D — Running `ALTER TABLE sales.transactions SET TBLPROPERTIES (delta.enableChangeDataFeed = true)` turns on Change Data Feed immediately, without rewriting any existing data files.; A new table can enable Change Data Feed at creation time by including `delta.enableChangeDataFeed = true` in its `TBLPROPERTIES` clause within the `CREATE TABLE` statement.; If legacy Change Data Feed is disabled on a table and then re-enabled later, the interval during which it was disabled remains a gap that cannot be queried through the change feed.
- A. Setting the `delta.enableChangeDataFeed` table property with `ALTER TABLE ... SET TBLPROPERTIES` is the documented way to turn on change tracking for an already-existing table. No backfill or rewrite of prior data files is needed; the table simply starts recording row-level changes from that point forward.
- B. Including `delta.enableChangeDataFeed = true` in the `TBLPROPERTIES` clause of a `CREATE TABLE` statement enables the feature from the table's very first commit. This is the standard pattern for new tables that are known to need downstream incremental consumers.
- C. A session-level default property only controls the setting applied to tables created after that session configuration is set. It has no retroactive effect on tables that already exist, so existing tables keep whatever setting they already had.
- D. This describes a real limitation of legacy Change Data Feed: toggling it off and back on leaves an unqueryable interval corresponding to the disabled period. Teams that need continuous coverage should avoid disabling it, or move to the automatic Change Data Feed mechanism instead.
- E. The property can be unset with `ALTER TABLE ... UNSET TBLPROPERTIES ('delta.enableChangeDataFeed')`, so it is not a permanent, one-way setting. Treating it as irreversible would incorrectly rule out a supported and commonly used administrative action.
- F. Change Data Feed for standard Delta tables has long been available without any Iceberg conversion, using the `delta.enableChangeDataFeed` table property described above. Requiring Iceberg storage is not a real prerequisite for this feature on Delta tables.
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.An UPDATE of one row produces one change row with the new values.Why is that wrong?
Each updated row produces two rows: an update_preimage with the old values and an update_postimage with the new ones. Downstream logic has to filter for or handle both.
Covered in What a change row looks like: updates and deletes exposed
2.You can keep delta.enableChangeDataFeed set while also using automatic change data feed.Why is that wrong?
Legacy and automatic CDF are mutually exclusive. Migrating means unsetting the legacy property.
Covered in Turning it on: legacy vs automatic change data feed
3.You can read a table's change feed from version 0 even if CDF was enabled later.Why is that wrong?
The feed only exists from the point it was enabled. Asking for an earlier version raises an error.
Covered in Reading a range of changes in batch
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“Incremental ETL pipelines that process only the rows that changed since the last pipeline run.”
↩︎ Why a table needs to expose its own changes“Legacy change data feed: Materializes changes during table writes. Only supports Delta Lake tables. Requires individual table configuration.”
↩︎ Turning it on: legacy vs automatic change data feed“Set the table property delta.enableChangeDataFeed = true in the ALTER TABLE command.”
↩︎ Turning it on: legacy vs automatic change data feed“You might see small increase in storage costs because changes might be recorded in separate files.”
↩︎ Turning it on: legacy vs automatic change data feed“Some operations, such as insert-only or full-partition deletions, don't generate change data files.”
↩︎ Turning it on: legacy vs automatic change data feed“Automatic change data feed computes row-level changes at query time rather than at write time, using row tracking on Delta Lake tables”
↩︎ Turning it on: legacy vs automatic change data feed“automatic change data feed improves write performance and reduces storage costs, compared to legacy change data feed.”
↩︎ Turning it on: legacy vs automatic change data feed“Databricks Runtime 19 or above”
↩︎ Turning it on: legacy vs automatic change data feed“For Delta Lake, only Databricks readers can query automatic change data feed.”
↩︎ Turning it on: legacy vs automatic change data feed“If the schema contains columns with the same names as these metadata columns, you can't use change data feed on a table.”
↩︎ What a change row looks like: updates and deletes exposed“When you read from the change data feed for a table, the query uses the schema for the latest table version.”
↩︎ What a change row looks like: updates and deletes exposed“Starting versions are required for batch reads.”
↩︎ Reading a range of changes in batch“Each change record includes the row data along with metadata that indicates whether the row was inserted, updated, or deleted.”
↩︎ Key concept“Contains: insert, update_preimage, update_postimage, delete.preimage is the value before the update, postimage is the value after the update.”
↩︎ Exam trap 1“You can't use both legacy and automatic change data feeds together.”
↩︎ Exam trap 2“If you specify a version before change data feed was enabled, it raises an error.”
↩︎ Exam trap 3“You can't use both legacy and automatic change data feeds together.”
↩︎ Checkpoint“preimage is the value before the update, postimage is the value after the update.”
↩︎ Checkpoint“Start and end versions are inclusive.”
↩︎ Checkpoint - 2.
“Rather than processing entire datasets, CDC generates feeds containing only the modified records.”
↩︎ Why a table needs to expose its own changes“Delta tables also generate their own CDC feed, known as a Change Data Feed (CDF)”
↩︎ Why a table needs to expose its own changes“Because snapshots don't capture record-level changes, identifying what changed requires comparing records across snapshots to infer inserts, updates, and deletes.”
↩︎ Checkpoint - 3.
“All operations that set or update table properties conflict with other concurrent write operations, causing them to fail.”
↩︎ Turning it on: legacy vs automatic change data feed - 4.
“If not specified all changes from start up to the current change are returned.”
↩︎ Reading a range of changes in batch
Also cited
“Notice that a single logical change (for example, moving the Spoon to a different warehouse) produces multiple events: a preimage and a postimage.”
↩︎ Prediction