Change Data Feed
Read the row-level inserts, updates and deletes made to a Delta table between two versions.
On this page
Show code in
Every code block on the page follows this.
You will learn
- What Change Data Feed records, and how to enable it
- The change types and metadata columns it returns
- How to read changes in batch and streaming
- How it powers incremental pipelines
Read first
- MERGE INTO and upserts · 4 min read
- The Delta transaction log · 8 min read
Comfortable with these? Read on.
delta.enableChangeDataFeed = true, Delta records row-level changes for every commit. You can read the inserts, updates (before and after images) and deletes between two versions, so downstream tables can process only what changed.Why it exists
Downstream tables built from a source table usually need only the rows that changed since the last run. Without row-level changes, you either reprocess everything or compare snapshots, both expensive. Reading appended files alone misses updates and deletes. Change Data Feed (CDF) gives you exact row changes.
Enabling it
ALTER TABLE customers SET TBLPROPERTIES (delta.enableChangeDataFeed = true); -- or at creation CREATE TABLE customers (...) TBLPROPERTIES (delta.enableChangeDataFeed = true);
Changes are recorded only from the version where it was enabled onward. For inserts and whole-file deletes, Delta derives changes from the log; for updates, merges and partial deletes, it writes extra change files in a _change_data folder.
What you get back
| Column | Meaning |
|---|---|
| Table columns | The row's values |
_change_type | insert, update_preimage, update_postimage or delete |
_commit_version | The table version of the change |
_commit_timestamp | When that version was committed |
customers, version 5 → 6
| id | tier |
|---|---|
| 1 | silver |
| 2 | gold |
| 3 | free |
changes in version 6
| id | tier | _change_type |
|---|---|---|
| 1 | silver | update_preimage |
| 1 | gold | update_postimage |
| 3 | free | delete |
| 4 | trial | insert |
Version 6 upgraded customer 1, deleted customer 3 and added customer 4. Customer 2 did not change and does not appear.
Reading changes
# Batch: changes in versions 5 through 10 changes = (spark.read.option("readChangeFeed", "true") .option("startingVersion", 5).option("endingVersion", 10) .table("customers")) # Streaming: continuously, from where the checkpoint left off stream = (spark.readStream.option("readChangeFeed", "true") .option("startingVersion", 5).table("customers"))
SELECT * FROM table_changes('customers', 5, 10); SELECT * FROM table_changes('customers', '2025-03-01 00:00:00');
Using it downstream
- 1Read changes since the last processed version (stored in your job state, or tracked by a streaming checkpoint).
- 2Drop
update_preimagerows unless you need them (for example to subtract old values from aggregates). - 3Keep the latest change per key within the batch: a row can change several times between runs.
- 4MERGE the result into the target: delete on
delete, upsert otherwise.
Iceberg offers a similar capability through changelog views (create_changelog_view procedure in Spark), computed from snapshots.
Common mistakes
Expecting changes before CDF was enabled
Ignoring multiple changes per key in a batch
Falling behind retention
Key takeaways
- CDF records row-level inserts, updates and deletes per commit.
- Enable it with delta.enableChangeDataFeed; changes start from then.
- Read with readChangeFeed or table_changes; columns include _change_type and _commit_version.
- Downstream: latest change per key, then MERGE.
Check yourself
3 questions1. How is an update represented in the change feed?
Show the answer
Two rows: update_preimage and update_postimage. Updates produce before and after images.
2. Can you read changes from before CDF was enabled?
Show the answer
No, only from the version where it was enabled. Changes are captured from enablement onward.
3. Which SQL function reads the change feed?
Show the answer
table_changes. table_changes(table, start, end) returns row changes.
Practice it
Interview problems that use this: write the PySpark, run it, and get graded on hidden tests.
Go deeper
Primary sources: Delta: change data feed