Repository navigation
Spark: Add scd_type2 option to create_changelog_view procedure - #17765
moomindani wants to merge 5 commits into
Conversation
|
@huaxingao @szehon-ho would one of you be able to take a look? This revives #15594, which the stale bot closed in May after ten weeks without a review; GitHub offers no reopen on that one, so this is a fresh PR from the same branch. It is rebased onto current The parts that most need a reviewer's judgment are the SCD Type-2 semantics rather than the plumbing:
If any of those should go the other way, I am happy to change them. The full rationale and a demo output are in the PR description. |
szehon-ho
left a comment
There was a problem hiding this comment.
Hi @moomindani , interesting. Still thinking but not entirely sure yet of the approach.
Iceberg tables typically have quite a short history, snapshots are usually expired quickly because of compliance, data storage.. the value of this will be quite short.
To me it make sense to track the Iceberg values in a separate SCD type 2 table for longer history?
|
Thanks @szehon-ho — that premise is right, and I think it is worth naming the options explicitly, because "a separate SCD Type-2 table" can be reached in more than one way and I am not sure which one you have in mind. On the horizon: the changelog scan can only see changes whose snapshots are still around, so a view built directly on it covers exactly the retention window and no more. How long that window is, though, is a choice rather than a fixed limit — That said, I would not build a feature on the assumption that the window is long. Retention gets chosen for storage cost, metadata size and compliance deletion, not for a downstream SCD Type-2 use case, so on most tables it will be short — which is your point, and I agree with where it lands. As far as I can see there are three ways to end up with a long-lived SCD Type-2 table:
This PR is the derivation step of option 3. It stores nothing itself; it turns changelog rows into version intervals — joining snapshot timestamps, applying the window function, dropping the Since the option needed documentation anyway, I wrote that up rather than just asserting it: Which of the three did you have in mind? If it is 1 or 2, that is a different feature from this one and I would not claim this PR covers it. If it is 3, this is the piece that makes it a few lines of SQL instead of a hand-rolled window query. |
Introduces a new `scd_type2 => boolean` parameter to the `create_changelog_view` Spark procedure that natively produces a Slowly Changing Dimensions Type-2 history view from an Iceberg changelog table. When enabled, the procedure adds three new columns to the view: - `_valid_from` (TIMESTAMP): commit timestamp when this version became active - `_valid_to` (TIMESTAMP, nullable): commit timestamp when superseded; NULL if still active - `_is_current` (BOOLEAN): true when _valid_to IS NULL AND _change_type != 'DELETE' UPDATE_BEFORE rows are filtered out as they are intermediate artifacts. DELETE rows are kept with `_is_current = false` to track hard deletes. Validation: scd_type2 requires identifier_columns and is incompatible with net_changes.
Adds the argument to the usage table, describes the three columns the SCD Type-2 view adds and why UPDATE_BEFORE rows are dropped while DELETE rows are kept, and documents how to build a long-lived SCD Type-2 table. The view only spans the changes the changelog scan can still see, so its history is bounded by snapshot retention. For history that must outlive snapshot expiration, the procedure derives each batch of versions and the user's own table stores them; the new section shows that loop with an explicit start snapshot and a MERGE that closes the previously open version.
ef48e01 to
40f83f4
Compare
|
@szehon-ho a nudge on this one — the question at the end of my 26 August comment is the only thing holding it, and I would rather have an answer either way than leave it sitting. To make it cheap to answer: the three routes to a long-lived SCD Type-2 table were (1) dual write, (2) SCD Type-2 as the base table, (3) derive from the changelog and store it. This PR is only the derivation step of (3) — it stores nothing itself. If you meant (1) or (2), those are different features and I do not claim this covers them. One correction: that comment pointed at And if your conclusion is that the retention window makes this not worth carrying inside the procedure, please say so and I will close the PR. Rebased onto current |
|
This pull request has been marked as stale due to 30 days of inactivity. It will be closed in 1 week if no further activity occurs. If you think that’s incorrect or this pull request requires a review, please simply write any comment. If closed, you can revive the PR at any time and @mention a reviewer or discuss it on the dev@iceberg.apache.org list. Thank you for your contributions. |
|
Not stale, this is waiting on one design question. @szehon-ho, did "a separate SCD Type-2 table" in your Aug 25 review mean deriving it from the changelog and storing the result (route 3 in my Aug 26 comment, which is the step this PR provides), or a different approach? The branch is mergeable and CI is green. |
Summary
Closes #15593
This revives #15594, which the stale bot closed on 2026-05-21 after ten weeks without a single review. GitHub does not offer a reopen on that PR, so this is a fresh PR from the same branch, rebased onto current
main. Spark 3.4 support was removed upstream in #14122, so the v3.4 copies are dropped — this now touches Spark 3.5, 4.0 and 4.1 (6 files instead of 8). Nothing else changed from the closed PR.Introduces a new
scd_type2 => booleanparameter to thecreate_changelog_viewSpark procedure that natively produces a Slowly Changing Dimensions Type-2 (SCD Type-2) history view from an Iceberg changelog table, without requiring users to write custom SQL.New columns added to the view
_valid_from_valid_toNULLif still active_is_currenttruewhen_valid_to IS NULL AND _change_type != 'DELETE'Example
Given inserts and updates to
products, the resulting view:Demo output (from actual Spark 4.1 test run)
Setup:
Without
scd_type2(existing behavior):Users must manually write window functions to derive validity intervals.
With
scd_type2 => true(this PR):Key differences:
UPDATE_BEFORErows filtered out (intermediate artifacts not meaningful in SCD Type-2)_valid_from/_valid_tocomputed automatically viaLEAD()window over snapshot timestamps_is_currentconvenience flag:trueonly when_valid_to IS NULL AND _change_type != 'DELETE'Querying current state:
No custom window logic needed.
Design choices
NULLsentinel for open-ended rows (not9999-12-31) — aligns with dbt and Databricks DLT_valid_from/_valid_tonaming — aligns with dbtdbt_valid_from/dbt_valid_toand Kimball semanticsUPDATE_BEFORErows filtered out — intermediate artifacts not meaningful in an SCD Type-2 view_is_current = false— tracks hard deletes_is_currentconvenience flag added — avoids the two-condition footgun (_valid_to IS NULL AND _change_type != 'DELETE')Constraints
identifier_columns(explicit or from table schema)net_changes = truecompute_updates = trueNote on snapshot expiration
The
scd_type2mode inherits the same snapshot expiration behavior as the existingcreate_changelog_view. If snapshots have been expired, the changelog scan itself omits the corresponding rows — the SCD Type-2 layer does not introduce any additional data loss or new failure modes. Specifically:scd_type2modes return the same reduced set of rows._valid_from/_valid_to/_is_currentare correct relative to the rows the changelog scan returns.FileNotFoundException. Both base andscd_type2modes fail identically.Changes
CreateChangelogViewProcedure.java— Spark v3.5, v4.0, v4.1TestCreateChangelogViewProcedure.java— Spark v3.5, v4.0, v4.1Test plan
testScdType2BasicInsertUpdateDelete— INSERT → UPDATE → DELETE lifecycle, verifies all 3 new columnstestScdType2CurrentRows— only non-deleted, most-recent rows have_is_current = truetestScdType2HardDelete— DELETE row has_valid_to = NULL,_is_current = falsetestScdType2RequiresIdentifierColumns— error when no identifier columnstestScdType2IncompatibleWithNetChanges— error when combined withnet_changes = truetestScdType2OutputSchema— verifies all 8 output columns in correct order