Exercise SCD2 history and incremental/CDC behavior under late data, deletes, duplicate delivery, crash/restart, and replay so historical semantics converge safely.
SCD/Incremental/CDC Tests for History, Late Data, Deletes, Idempotency, and Restart
Build an identity and access model for AtlasMart that separates humans from services, eliminates shared credentials, enforces environment boundaries, and proves least privilege with allow/deny evidence.
Learning outcomes
Test SCD2 interval correctness and event-time dimension resolution.
Prove late facts use historical state, not the current dimension row.
Test CDC crash/restart with atomic target/ledger/watermark commits.
Verify duplicate delivery is idempotent and deletes/corrections have explicit semantics.
Separate ordering guarantees supplied by a source from assumptions invented by a pipeline.
Chapter 24 begins from the governed AtlasMart state produced by Chapters 01–23: 10 current paid lines, 8 orders, 12 units, 820 USD gross revenue, 495 USD cost, and 325 USD gross profit. The current fact grain remains one accepted current paid order line; Chapter 20 metric definitions remain authoritative; Chapter 21 certified marts remain dependent on conformed assets; Chapter 22 security controls remain in force; and Chapter 23 ownership/lineage metadata supplies the change context. A test may block, quarantine, alert, or document a failure, but it must not silently change business semantics to make the suite green.
Runtime:
Python 3.13.5 + SQLite 3.46.1.
Storage: in-memory SQLite.
Time zone: UTC. Currency: USD.
Source/warehouse grain: one paid order line
identified by (order_id, line_id), with a separate
immutable delivery/event ID.
Security: synthetic customer IDs only.
Cost: free/local, no managed service.
Non-guarantee: passing these fixtures proves
the encoded contracts for this dataset/runtime; it does not
prove unknown business rules, external source truth, or
vendor-specific behavior not represented by the fixture.
1. The realistic problem: history can be wrong even when current totals match
AtlasMart’s C001 customer history contains SMB, Growth, then
Mid-Market versions. A late fact with event time
2026-09-18T15:00Z arrives after C001 is currently
Mid-Market. If the loader resolves “current customer row,” the
fact still contributes the correct 75 USD to total revenue but
lands in the wrong historical segment. Aggregate control totals
alone cannot detect this semantic error.
2. SCD tests: interval shape first
SELECT COUNT(*) AS overlapping_pairsFROM dim_customer_history aJOIN dim_customer_history b ON a.customer_id = b.customer_id AND a.customer_sk < b.customer_skWHERE a.effective_from < COALESCE(b.effective_to,'9999-12-31T00:00:00Z') AND b.effective_from < COALESCE(a.effective_to,'9999-12-31T00:00:00Z');
The expected count is 0. The negative fixture moves the
Mid-Market start backward to 2026-09-18T20:00Z,
overlapping the Growth interval until midnight. The detector
must fail before the repair. Half-open intervals remain the
Chapter 7 convention:
effective_from ≤ event_time < effective_to.
3. Boundary cases must be explicit
| Event time | Expected C001 segment | Reason |
|---|---|---|
2026-09-18T08:29:59Z |
SMB | Before Growth effective start |
2026-09-18T08:30:00Z |
Growth | Inclusive lower bound |
2026-09-18T23:59:59Z |
Growth | Before exclusive upper bound |
2026-09-19T00:00:00Z |
Mid-Market | Exactly at next interval start |
A test suite that exercises only “middle of interval” timestamps can miss off-by-one/boundary bugs that systematically misclassify facts at change instants.
4. Controlled failure: current-row lookup for a late fact
late_fact_current_lookup_is_wrong observed current segment: Mid-Market event-time segment: Growth expected repair: resolve as of 2026-09-18T15:00:00Zlate_fact_event_time_lookup PASS -> Growth
The negative test deliberately proves the unsafe approach differs from the correct result. “Current row” is convenient but violates historical reconstruction.
5. CDC restart test: watermark must not lead the committed target
BEGIN upsert target row for source_seq = 208 insert delivery ledger row for source_seq = 208 update committed watermark to 208COMMIT# on any exception before COMMIT:ROLLBACK
The target write, delivery ledger, and watermark are one transaction in this local fixture. A production source/target boundary may require a different transactional mechanism, but the invariant remains: do not acknowledge/advance progress past data that is not durably represented in the target.
6. Executed crash → restart → duplicate delivery trace
before watermark 207attempt 1 rolled_back (simulated crash)target rows for E208 0watermark after crash 207attempt 2 appliedduplicate redelivery duplicate_ignoredtarget rows for E208 1final watermark 208
This is convergence under at-least-once delivery, not magical exactly-once transport. The pipeline obtains an exactly-once effect for this event because stable identity, transactional state, and deduplication cooperate.
7. Deletes and corrections need tests, not assumptions
Earlier chapters established tombstone/inactivation and revision semantics. A CDC test suite should therefore include insert, update/correction, delete/tombstone, duplicate delivery, late delivery, and out-of-order delivery fixtures. The correct expected result depends on source guarantees: transaction/sequence ordering, before/after images, key reuse, and whether deletes are physical, soft, or absent. A test cannot infer missing source semantics safely.
8. Idempotency is observed through repeated state, not a function name
Calling a task upsert_sales does not prove
idempotency. Replay the same event/batch and compare target
keys, control totals, history intervals, ledger rows, and
checksum. A non-idempotent “increment quantity” update may
succeed twice and corrupt measures without creating duplicate
keys.
9. History controls and aggregate controls are complementary
The canonical 10 current paid lines, 8 orders, 12 units, 820 USD gross revenue, 495 USD cost, and 325 USD gross profit can remain correct while the late C001 fact is assigned to Mid-Market instead of Growth. Therefore acceptance needs both quantitative reconciliation and semantic/history assertions. Conversely, perfectly shaped SCD intervals do not prove total revenue is complete. Layer the tests by failure mode.
10. Restart/replay test matrix
| Injected condition | Expected result |
|---|---|
| Crash before transaction commit | No target/ledger/watermark effect |
| Retry same source sequence | One committed effect |
| Duplicate after success | Ignored by ledger/stable identity |
| Late event | Applied by source order/version rules; event-time semantics preserved |
| Delete/tombstone | Current representation changes per explicit delete policy; audit evidence retained |
| Backfill/reprocess | Same deterministic target state/checksum for same accepted evidence |
11. Production judgment and bridge
Correctness: history tests protect event-time semantics that totals cannot. Ordering: only assert guarantees your source provides. Schema evolution: CDC payload changes require contract/version tests before replay. Throughput: test batch/window strategies at representative scale, but never advance watermarks early for speed. Security: event logs/before-images can contain sensitive values. Rollback: preserve raw/ledger evidence so a bad transformation can be corrected and replayed. Lesson 4 reconciles the resulting target to trusted source/metric controls.
12. Verification checklist
- Confirm the valid SCD fixture has zero overlaps and the bad fixture produces at least one overlap.
- Confirm the late C001 fact resolves to Growth, not current Mid-Market.
- Confirm the simulated crash leaves watermark 207 and no E208 target row.
- Confirm retry commits one E208 effect and duplicate redelivery is ignored.
- Confirm replay tests compare business state, not only task exit status.
Knowledge check
Acceptance questions
- Why can aggregate totals pass while SCD history is wrong?
- What is unsafe about advancing a watermark before commit?
- Does at-least-once delivery prevent exactly-once target effects?
- Why must delete semantics come from the source contract?
Review the answers
1. A fact can carry the right measure but the wrong historical dimension version.
2. A crash can skip uncommitted records permanently on restart.
3. No; stable identity, atomic state, and deduplication can make repeated delivery converge to one effect.
4. Different sources emit hard deletes, tombstones, soft flags, or nothing; the pipeline cannot invent the missing meaning.
Authoritative references
- Python — unittestStandard-library support for deterministic fixtures, assertions, setup/teardown, and automated test suites.
- SQLite — CREATE TABLE / constraintsAuthoritative syntax and semantics for NOT NULL, CHECK, UNIQUE, PRIMARY KEY, and table constraints used in the local lab.
- SQLite — Foreign Key SupportExplains foreign-key behavior and the need to enable enforcement explicitly in SQLite connections.
- SQLite — TransactionsUsed to demonstrate that target writes, delivery ledger, and watermark advancement must commit atomically for safe restart.
- SQLite — UPSERTReference for conflict-aware idempotent target application in the CDC fixture.
- Python — hashlibUsed to fingerprint deterministic test evidence rather than compare unstable timestamps or environment-specific logs.
13. Lab cleanup/reset
The restart fixture is in memory. Closing the process discards target, ledger, and watermark state. Rerun to reproduce the crash/retry trace deterministically.