Skip to content
Good engineers know 3 min read · Delta 2 practice problems ↓

Change Data Feed

Read the row-level inserts, updates and deletes made to a Delta table between two versions.

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

Comfortable with these? Read on.

TL;DR With 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

Spark SQL
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

ColumnMeaning
Table columnsThe row's values
_change_typeinsert, update_preimage, update_postimage or delete
_commit_versionThe table version of the change
_commit_timestampWhen that version was committed

customers, version 5 → 6

idtier
1silver
2gold
3free

changes in version 6

idtier_change_type
1silverupdate_preimage
1goldupdate_postimage
3freedelete
4trialinsert

Version 6 upgraded customer 1, deleted customer 3 and added customer 4. Customer 2 did not change and does not appear.

Reading changes

PySparkSpark SQL
# 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

  1. 1
    Read changes since the last processed version (stored in your job state, or tracked by a streaming checkpoint).
  2. 2
    Drop update_preimage rows unless you need them (for example to subtract old values from aggregates).
  3. 3
    Keep the latest change per key within the batch: a row can change several times between runs.
  4. 4
    MERGE the result into the target: delete on delete, upsert otherwise.
Note: CDF data follows the table's retention: VACUUM removes old change files too. Consumers must keep up within the retention window or restart from a full snapshot.

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

It records changes only from that version on.

Ignoring multiple changes per key in a batch

Apply only the latest, or ordering bugs appear.

Falling behind retention

Vacuumed change files are gone; plan a full reload path.

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 questions

1. 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.

Solve: Net Changes from a Change Feed →

Go deeper

Primary sources: Delta: change data feed