Skip to content

Incremental Modeling

Group 1 - Part 2

Every model in mediapulse_base is currently a view or a full-rebuild table. That's fine at this data volume, but fct_ad_impressions and fct_streaming_events are exactly the kind of high-volume, append-heavy fact tables that stop being practical to fully rebuild once real data volumes show up.

Exercise

Step 1 - Pick your candidate and understand its grain

  • Step complete

Open mediapulse_base/models/marts/ads/fct_ad_impressions.sql. Confirm its grain (one row per what?) and identify a column that only ever increases over time (a good incremental filter candidate).


Step 2 - Convert it to incremental

  • Step complete

Add an incremental config() to the model: materialized='incremental', a unique_key, and an incremental_strategy. Add the is_incremental() guard so a full run still works the first time.

Hint: the moving parts of an incremental model

An incremental model needs a materialization config, a unique_key so dbt knows which rows to update versus insert, and an {% if is_incremental() %} block that's skipped entirely on the first (full) run. See Configure incremental models for the full anatomy, and About incremental strategy for what merge, append, delete+insert, and insert_overwrite each actually do on Snowflake.


Step 3 - Justify your strategy choice

  • Step complete

Explain why you picked merge over append (or vice versa) for this specific table, referencing whether ads.impressions rows can ever be corrected/updated after the fact versus only ever appended.


Step 4 - Prove it's actually incremental

  • Step complete

Run the model twice in a row and compare what changes. What evidence would convince you it processed less data on the second run, not just that it ran without error?


Deliverable: your converted model, your strategy justification, and the evidence from your two-run comparison.

Extension

Step 1 - Handle a full-refresh scenario

  • Step complete

Identify a realistic situation where your incremental logic would need --full-refresh to produce correct results (e.g. a schema change to the model, or a change to the allocation logic that needs to be applied retroactively to historical rows). Explain why an ordinary incremental run wouldn't fix it.

Hint: full-refresh is a reset, not a patch

--full-refresh drops and rebuilds the entire incremental table from scratch, ignoring the is_incremental() filter for that run - it's the right tool when the logic changed, not just the data. See Configure incremental models for when dbt recommends reaching for it.


Step 2 - Convert a second, harder candidate

  • Step complete

fct_streaming_events joins impressions-equivalent watch events to two other models (content catalog and subscriptions) without a date filter today. Convert it to incremental, and work out how your is_incremental() filter needs to account for the fact that a late-arriving watch event could reference a watched_at timestamp earlier than your current max, but still needs to be picked up (batched_at may help here - read the model's columns before deciding).


Step 3 - Compare strategies on paper

  • Step complete

Without implementing it, describe what would change about your fct_streaming_events model if Snowflake volumes grew large enough that insert_overwrite (partition-based) made more sense than merge. What would you need to partition on, and what would you give up by switching?


Deliverable: your full-refresh scenario writeup, your converted fct_streaming_events model with late-arrival handling, and your merge-vs-insert_overwrite comparison.

Done?

You've taken mediapulse_base from an all-views-and-tables project to one with a working incremental model, and you can justify every config choice you made along the way.

That's Group 1 complete - well done. You've refreshed staging-to-marts modeling, closed real test and governance gaps, written macros, built a snapshot, and gone incremental, all against a real project rather than a toy one.