Skip to content

[SPARK-59165][SDP] SCD2 Ignore-null support; Introduce version map in _cdc_metadata - #58483

Open
AnishMahto wants to merge 10 commits into
apache:masterfrom
AnishMahto:SPARK-59165-scd2-version-map-schema
Open

[SPARK-59165][SDP] SCD2 Ignore-null support; Introduce version map in _cdc_metadata#58483
AnishMahto wants to merge 10 commits into
apache:masterfrom
AnishMahto:SPARK-59165-scd2-version-map-schema

Conversation

@AnishMahto

@AnishMahto AnishMahto commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

This is a stacked PR. See incremental diff here: AnishMahto/spark@SPARK-59164-nested-schema-diff...SPARK-59165-scd2-version-map-schema

Previous PR in stack: #58465

What changes were proposed in this pull request?

In order to support ignore-null in SCD2, we need to persist additional metadata per row that tells us which column values were actually authored by the upsert event that created the row, and which columns were unauthored by the same upsert event and instead need to inherit a value from a previous row.

This PR is concerned with simply defining the version map schema/concept, and adding it to the _cdc_metadata per row. The version map will not be used yet, but will start materializing in both old and new AutoCDC tables.

API for ignore-null is not exposed yet, so a non-null version map will never be created yet.

Why are the changes needed?

To support the ignore-null feature for AutoCDC SCD2 (as per the approved SPIP), we need to introduce a new map data structure to persist column authorship. This is a per-row data structure and holds operational metadata, so it naturally fits in _cdc_metadata.

Does this PR introduce any user-facing change?

Yes. The (nullable) version map field is added to the _cdc_metadata column. That means existing AutoCDC target tables that do not contain this subfield will undergo schema evolution on their next pipeline run after this change.

Nested-field schema evolution is supported in SDP as of the previous PR in this stack, and the evolution case is captured via unit tests using the in-memory DSv2 catalog.

How was this patch tested?

Unit tests.

Many existing unit test assertions on final table contents were updated to account for the (null) version map.

Was this patch authored or co-authored using generative AI tooling?

Yes, using Claude Opus 4.6.

@AnishMahto
AnishMahto force-pushed the SPARK-59165-scd2-version-map-schema branch 2 times, most recently from ab72b27 to 2cb3476 Compare September 2, 2026 21:56
@AnishMahto
AnishMahto marked this pull request as ready for review September 2, 2026 21:58
@AnishMahto
AnishMahto force-pushed the SPARK-59165-scd2-version-map-schema branch from 2cb3476 to ac73d9a Compare September 3, 2026 02:27
private def scd2Meta(recordStartAt: Long): Row = Row(recordStartAt)
private def scd2Meta(recordStartAt: Long): Row = Row(recordStartAt, null)

test("legacy SCD2 CDC metadata schema evolves to include the version map") {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The major focus for this PR should be - what should we do with existing AutoCDC tables created before version map is added to the CDC metadata column?

What I rely on in this PR, as is illustrated by this test, is SDP + DSv2's ability to handle schema evolution for nested columns. That means existing AutoCDC tables will gain a version map sub-field in their CDC metadata column, set to null, on their next run after these changes lands.

Now what is technically not guaranteed, is whether each popular connector actually supports and respects the table alters and TableChanges we emit, via DSv2. If they don't, then those tables will be not be able to be refreshed going forward, on the first Spark version that includes these changes. But from SDP's perspective, we are fully compliant with and using DSv2 API.

Of the connectors that do support the MERGE operation (which is a prerequisite for AutoCDC anyway), I'm not aware of any that don't support nested column evolution via DSv2 - ex. both Delta and Iceberg do support nested evolution. But technically Parquet tables in HMS do not support nested schema evolution AFAIK. Now Parquet does not support MERGE either, so an AutoCDC table could never have been materialized using it.

Alternative option
If we're not willing to rely on the assumption that all AutoCDC compatible table formats and catalogs support nested schema evolution via DSv2, then another option is too just support two variants of the CDC metadata column schema going forward. Existing tables will use the schema definition that does not include the version map, and new tables will be created with the schema definition that includes the version map.

This does however mean:

  • We need to support both schema variants forever going forward
  • Existing AutoCDC tables are not allowed to convert to ignore-null. That also means they will never be eligible for AutoCDC multiflow, which relies on ignore-null
  • We'll need to add extra logic to check the existing table's schema, and determine which schema variant its using
  • The Scd2BatchProcessor code will have to be written in a way that supports both types of schemas

Which I am generally not a fan of, and would prefer to avoid if justifiable.

private[pipelines] val recordStartAtFieldName: String = "__RECORD_START_AT"

/** CDC metadata field for the ignore-null version map. */
private[pipelines] val versionMapFieldName: String = "__VERSION_MAP"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Authorship map could also be a good name instead of version map.

@AnishMahto

Copy link
Copy Markdown
Contributor Author

@jose-torres for review. This is a really small logical change, a bunch of tests just needed to be updated to adjust for the new __cdc_metadata schema.

Speaking of which though, there is one important migration discussion I started a thread on here that could change the approach we take: #58483 (comment).

diffSchemas previously compared top-level fields only. When a struct
column gained or lost nested fields, the entire column type was emitted
as an UpdateColumnType -- a change many DSv2 connectors reject because
the semantics of replacing a whole struct type are ambiguous.

This patch makes diffSchemas recurse into StructType columns: nested
field additions become addColumn with multi-part field paths, deletions
become deleteColumn, and type/nullability changes become the
corresponding nested updateColumnType/updateColumnNullability. This is
the DSv2-idiomatic way to evolve struct columns and is supported by
Delta, Iceberg, and other connectors that handle nested schemas.
@AnishMahto
AnishMahto force-pushed the SPARK-59165-scd2-version-map-schema branch from ac73d9a to e25ff00 Compare September 3, 2026 20:43
@AnishMahto
AnishMahto force-pushed the SPARK-59165-scd2-version-map-schema branch from e25ff00 to ab65e68 Compare September 4, 2026 06:15
Add a nullable __VERSION_MAP field (Map<String, Boolean>) to the SCD2
_cdc_metadata struct. This field will track per-column authorship for
ignore-null semantics in a future change; for now it is always null.

Source changes:
- Introduce Scd2VersionMap with mapType and authorship contract docs.
- Add versionMapFieldName, versionMapOf to Scd2BatchProcessor companion.
- Extend cdcMetadataColSchema and constructCdcMetadataCol with the new
  field. All four call sites pass versionMap explicitly (no default).
- Decomposition tails and tombstones always receive a null version map
  (synthetic rows carry no authorship claim).

Test changes:
- Update all Row(...) constructions for CDC metadata structs across
  Scd2BatchProcessorSuite, Scd2BatchProcessorMergeSuite,
  Scd2ForeachBatchHandlerSuite, and 7 graph execution test suites.
- Fix schema-preservation tests to use the production two-field
  cdcMetadataInnerSchema.
- Update scd2MetadataDdl in AutoCdcGraphExecutionTestMixin.
@AnishMahto
AnishMahto force-pushed the SPARK-59165-scd2-version-map-schema branch from ab65e68 to bdbe370 Compare September 4, 2026 16:52

@jose-torres jose-torres left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The contents of this incremental diff make sense to me, although I'm not quite clear on what it is that it's a stack on top of - is there another PR before it I should also be looking at?

@AnishMahto

Copy link
Copy Markdown
Contributor Author

Just added an explicit link to the previous PR in the stack! Feel free to take a look, but I tagged @szehon-ho on that one since it's a more general SDP bug fix.

It affects this PR, but is not AutoCDC specific.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants