Design the Orchestration and Job Flow

Estimated read: 5 min

Sections 2 through 4 built everything StepRight needs to turn raw source data into five gold tables finance, marketing, merchandising, growth, and operations can query – but every one of those runs happened because someone clicked “start” in the workspace UI. Nothing here runs on a schedule yet, and nothing stops bad bronze data from silently reaching the gold layer. This lecture designs the Lakeflow Job that fixes both problems before Lecture 4 builds it for real.

From three manual runs to one daily job

As things stand, producing a fresh day of gold tables means three separate manual steps: start the bronze pipeline, wait, then start whatever pipeline builds silver and gold, and hope nobody forgets to check whether bronze actually landed clean data first. That’s exactly the kind of babysitting a legacy Control-M or Autosys batch chain existed to eliminate, and it’s what a Lakeflow Job does here: one scheduled trigger, a defined task order, and automatic failure propagation if an early step goes wrong.

The manifest for this section settles on four tasks: run ingestion, DQ check, run transformation, and report. That ordering is deliberate, not arbitrary – the DQ check sits between ingestion and transformation, not after everything, because the whole point of gating is to stop bad bronze data before silver’s AUTO CDC merges or gold’s materialized views build on top of it, not to discover the problem after every downstream table has already been rebuilt from it.

Why the DQ gate can’t live inside one pipeline

Section 2’s bronze pipeline (steprightproject-bronze-cdc) was defined with libraries: - glob: {include: transformations/**} – a wildcard broad enough that it would just as happily pick up Section 3’s silver_cdc.py and Section 4’s gold_reporting.py the moment those files landed in the same transformations/ folder, folding bronze, silver, and gold into one growing pipeline DAG. That worked fine while nothing needed to happen between layers. It stops working the moment orchestration needs a real checkpoint: a Lakeflow Declarative Pipelines update runs its whole graph as one unit, and there’s no supported way to pause a pipeline mid-DAG for an external Python check and only continue if that check passes.

A Lakeflow Job task, by contrast, can be exactly that checkpoint – but only if ingestion and transformation are two separate pipeline resources the job can trigger, inspect, and gate independently.

Splitting one pipeline into two scoped ones

This section narrows steprightproject-bronze-cdc’s glob to just the bronze files it was always conceptually about, and introduces a second pipeline, steprightproject-silver-gold, scoped to everything Sections 3 and 4 added:

Pipeline Glob scope Built in
steprightproject-bronze-cdc transformations/bronze_*.py, transformations/bronze_*.sql Section 2
steprightproject-silver-gold transformations/silver_*.py, transformations/gold_*.py, transformations/gold_*.sql Sections 3-4

Bronze Lecture 4 already flagged this as “a reasonable choice once a real team owns file-based sources and CDC sources separately and wants independent deploy/monitor/alert boundaries” – the same reasoning extends one step further here: ingestion and transformation now need independent trigger boundaries too, so a job task can run one, check the result, and only run the other if that check passes. transformations/dq_helpers.py, a shared module imported by both pipelines rather than a table-producing file itself, isn’t matched by either glob and needs no change.

The four-task DAG

flowchart TD
    A[run_ingestion<br/>pipeline_task] --> B[dq_check<br/>python task]
    B -->|ALL_SUCCESS| C[run_transformation<br/>pipeline_task]
    C --> D[report<br/>python task]

run_ingestion triggers steprightproject-bronze-cdc; dq_check (Lecture 2) reads that run’s quarantine counts and fails the task outright if the quarantine rate crosses a threshold; run_transformation, gated with run_if: ALL_SUCCESS on dq_check, triggers steprightproject-silver-gold only when bronze passed; report (Lecture 3) runs last and prints a summary – bronze, silver, gold counts, quarantines, run date – straight into the job run’s own logs. Part 1, Section 10’s multi-task DAG lecture covered run_if conditions in general; this is the concrete case they were building toward.

One run_date, threaded through every task

Every layer this job touches is date-scoped in some way – bronze’s Auto Loader batches, silver’s CDC merges, gold’s daily rollups – and all four tasks need to agree on which date they’re processing, especially if the job runs past midnight or gets manually re-triggered for a specific day. The fix is a single job parameter, run_date, declared once at the job level and referenced by every task rather than each task computing current_date() independently:

parameters:
  - name: run_date
    default: ""

This is the same job-parameter mechanic Part 1, Section 10, Lecture 3 introduced, applied here so dq_check and report – the two Python tasks – read the exact same run_date the scheduled trigger fired with, the Databricks-native version of a batch date variable a legacy Control-M chain would pass down through every step of a nightly run rather than letting each script independently ask the OS for “today.”

What’s next

Lecture 2 builds dq_check: the Python task that reads bronze’s quarantine counts for run_date and decides, with real thresholds, whether run_transformation gets to run at all. Lecture 3 builds report. Lecture 4 assembles all four tasks into the actual job resource and turns this design into something that runs on a schedule.


| ← Previous: StepRight - Orchestration and Job Scheduling | Next: Job Pipeline Data Quality and Health Check Implementation → | |:—|—:|


Back to top

Independent, self-authored Databricks companion notes.

This site uses Just the Docs, a documentation theme for Jekyll.