Add initial approach doc for CDC lakehouse assignment - #17
Open
MeghaNandish wants to merge 3 commits into
Open
Conversation
MeghaNandish
marked this pull request as draft
August 15, 2026 14:47
…ouse loader (snapshot + SCD2), schema-change guard, validation suite, catalog
MeghaNandish
marked this pull request as ready for review
August 16, 2026 09:18
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
CDC Lakehouse — Wallet/Payments Domain
Implements a small but complete CDC lakehouse pipeline: Postgres source → polling-based CDC → append-only JSON-lines lake → Postgres warehouse (current-state snapshot + SCD2 history) → schema-change guard → validation suite → dataset catalog.
All commands below assume repo root as cwd and the
cdc-postgresDocker container running (docker run --name cdc-postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=wallet_db -p 5432:5432 -d postgres:16).How to run
1. Source Schema
Domain: wallet / payments / transfers (
submission/megha/db/schema.sql).Every table carries
updated_atand a soft-deleteis_deletedflag — this is the contract the CDC layer depends on (see §2). Acdc_checkpointtable tracks per-table extraction cursors.2. CDC Strategy
Implemented as polling-based CDC (
submission/megha/cdc_extract.py), not WAL/Debezium — an explicit, documented simplification given the time budget. Each run:(updated_at, id)cursor per table fromcdc_checkpoint.(updated_at, id) > (last_updated_at, last_id)— a compound cursor, so multiple rows sharing an exact timestamp are never silently skipped.lake/{table}.jsonl(op: upsertorop: delete, driven byis_deleted).Verified idempotent: running the extractor twice with no source changes produces 0 new records both times (see
test_extractor_is_idempotent_with_no_new_data).Known limitation (documented, not hidden): because this is poll-based, a row that changes twice between two polls is only captured once, at its state at poll time — intermediate states are lost. A WAL-based approach (Debezium / logical replication) would capture every individual write. This was observed directly in testing: a wallet that was inserted and then soft-deleted within the same poll window shows up in the lake as a single
deleterecord, never as aninsertfollowed by adelete.3. Lake / Warehouse Modeling
lake/*.jsonl): append-only, one file per source table, one JSON record per captured change. This is the full-history source of truth and the basis for arbitrary-point replay.warehouse.*schema in the same Postgres instance, kept logically separate from source data via schema namespacing):warehouse.customer,.wallet,.transfer,.wallet_balance_history,.payment_attempt) — one row per entity, latest state, upserted from the lake.warehouse.wallet_history,.transfer_history) — for the two entities where restore/time-travel matters most.valid_from/valid_to/is_currenttrack state transitions; a row is only closed out and replaced if its tracked columns actually changed (avoids junk history entries from metadata-only touches).warehouse.load_logmakes the loader idempotent — verified bytest_loader_is_idempotent.4. Schema-Change Safety
submission/megha/schema_guard.pysnapshots the live source schema (information_schema.columns) as a baseline on first run (--init). Every subsequent run diffs live vs. baseline:This is meant to gate
cdc_extract.pyin a real pipeline (e.g. CI/cron step:schema_guard.py && cdc_extract.py). Proven end-to-end intest_dropped_column_is_detected_as_breaking, which adds a real column, initializes a baseline, drops it, and asserts the halt + diagnostic message.5. Time Travel / Restore
Recent state: query
warehouse.wallet_history/warehouse.transfer_historyfor the row wherevalid_from <= T AND (valid_to > T OR valid_to IS NULL)— reconstructs state as of any timestamp T without touching the lake.Older / full restore: replay
lake/{table}.jsonlin order up to a timestamp into a scratch table — the lake retains complete history regardless of what the warehouse's SCD2 tables have compacted.6. Validation / Parity
submission/megha/test_pipeline.py— 15 pytest tests across the 4 required categories, all passing:op: delete.valid_toset, idempotent loading.Built Red → Blue → Green per requirement: each test was written to fail against the missing/naive behavior first, then made to pass with the minimal necessary implementation.
7. Catalog Exposure
submission/megha/catalog.yaml— lists all 14 datasets (5 lake, 7 warehouse, 2 operational) with location, format, owner, source table, update cadence, and grain. Validated as parseable YAML. In production this would be published to a real catalog (DataHub / Glue / Unity Catalog); this file captures the equivalent metadata manually, scoped to what this assignment produces.Responsible AI Usage
I used an AI assistant (Claude) to help scaffold this solution — generating initial drafts of the schema DDL, CDC extractor, warehouse loader, schema-change guard, test suite, and this write-up, based on my own architecture decisions (domain choice, polling-CDC vs. WAL, SCD2-for-wallet/transfer-only, JSON-lines vs. Parquet). I ran every script and test myself, debugged real failures that came up during testing (e.g. a path-resolution bug in the test suite, a leftover column from an earlier failed test run), and verified the actual output at each step (row counts, SCD2 history contents, exit codes) rather than trusting generated code blindly. The reasoning behind each design decision above reflects my own understanding, checked against what I observed running the pipeline.