The Dependency Graph: Lineage Before You Touch Production
Note
BLAST RADIUS REQUESTED: Column definition changed upstream. Thirty-seven downstream assets discovered. Publication paused until ownership and impact are known.
The first question in a data incident is rarely what failed?
It is usually:
What else will be wrong if we change this?
Without lineage, the answer is a search through SQL files, dashboards, notebooks, and tribal memory. That search is slow, incomplete, and especially dangerous when the pipeline is already under pressure.
Lineage turns a table into a dependency graph. It shows which job produced a dataset, which datasets a job read, and which consumers depend on the output. More importantly, it gives an operator a bounded answer before a migration, backfill, or rollback begins.
This is the first post in the next phase of the Reliable Data Systems series: making the platform explain its own state.
A Table Is Not the Whole Story
Consider a small warehouse flow:
|
|
If raw.payments.currency becomes null, the immediate failure may be in ingestion. The business impact is somewhere else:
- Finance sees incomplete revenue by currency.
- Customer lifetime value becomes incomparable across regions.
- A model feature changes distribution without a model deployment.
The graph makes the difference visible. A pipeline log can tell you that a task succeeded. Lineage can tell you which products are now untrustworthy.
Three Entities, Two Kinds of Lineage
The useful minimum is not a complicated catalog. It is a consistent vocabulary.
| Entity | Question it answers | Example |
|---|---|---|
| Job | What activity did work? | dbt build:finance |
| Run | Which execution of that activity? | run-2026-06-15-001 |
| Dataset | What data went in or came out? | warehouse.fct_revenue |
OpenLineage uses this Job/Run/Dataset model and records state transitions such as START, COMPLETE, FAIL, or ABORT. Extra context is attached as facets: schema, ownership, input statistics, data quality results, or a query plan.
That distinction creates two complementary views:
Design-Time Lineage
Design-time lineage is inferred before execution:
fct_revenuedepends onstg_payments- A model selects
amountandcurrency - A dashboard queries
fct_revenue
It is useful for planning and code review. It can be extracted from dbt manifests, SQL parsers, workflow definitions, and declared contracts.
Run-Time Lineage
Run-time lineage records what actually happened:
- Run
abc123read partition2026-06-14 - It wrote 4.8 million rows
- The output schema changed from version 3 to version 4
- The run completed with a 2.1% null rate in
currency
Run-time lineage is the evidence needed during an incident. A static dependency file cannot prove which partition was touched by a specific backfill.
A Small Lineage Event
An implementation does not need to begin with a full catalog. Emit a run event at the boundary of each meaningful data job.
The exact transport and integration can vary. The durable design decision is to record the same identifiers every time. If one tool calls a dataset prod.fct_revenue and another calls it warehouse.finance.fct_revenue, the graph will split into two imaginary tables.
Lineage Quality Has Its Own Tests
Lineage is not automatically trustworthy because it exists.
Test the graph like any other production output:
| Check | Failure it catches |
|---|---|
| Every critical output has an owner | Orphaned datasets during an incident |
| Every production run emits a terminal event | Runs that appear permanently active |
| Inputs and outputs use canonical names | Duplicate nodes for the same table |
| Schema facets match the published schema | Stale column-level lineage |
| Critical dashboards have upstream paths | Incomplete blast-radius analysis |
Start with table-level lineage. Add column-level lineage only where it changes a decision, such as regulated fields, financial metrics, or high-risk model features. A graph that is 80% complete and clearly marked is more useful than a graph that claims false precision.
The Impact Analysis Protocol
Before changing a production dataset:
- Identify the exact dataset, field, partition, or event version being changed.
- Traverse downstream consumers and classify them as critical, important, or informational.
- Find an owner for every critical consumer.
- Check the last successful run, data-quality result, and schema version.
- Choose the migration or rollback window based on actual usage, not file search alone.
- Publish the change with a link to the affected graph and a verification plan.
After the change, compare the observed graph and metrics with the predicted impact. If the change touched an unexpected consumer, the lineage system has found a platform defect—not just an application defect.
The Lineage Rule
Lineage is operational state, not decorative documentation.
It should be emitted by the jobs that know what they read and wrote, enriched with ownership and quality metadata, and used before anyone makes a risky change. The goal is not to draw a beautiful graph. The goal is to make the blast radius small enough to act on.
References: OpenLineage overview, OpenLineage API events, and OpenLineage facets.
Next: why a database copy is not a change stream, and what log-based CDC really guarantees.