Incremental Models and Transformation Tools

Topics Covered

What a Transformation Tool Actually Does

The honest description

Refs, and where the graph comes from

Templating is the smallest part

Materialisation is the real abstraction

The estate, in numbers

What the tool does not do

Where the tool's own time goes

What you own instead

A transformation tool is the least glamorous piece of a modern data platform and the one that decides what the rest of it costs.

The honest description

A transformation tool takes a directory of SQL files, works out what order to run them in, wraps each one in the right kind of create statement, runs them against your warehouse, and then runs a second set of queries that check whether the results look the way you said they would. That is the whole product. It does not have an execution engine, it does not move data, and it does not make your warehouse faster. It writes SQL and sends it somewhere else to run.

Saying it that plainly matters, because a surprising amount of confusion about these tools comes from expecting them to be engines. When a model is slow, the tool is almost never the reason. The tool generated a statement and the warehouse took eleven minutes to execute it, and the eleven minutes belong to the shape of that statement and the layout of the data it reads. Debugging a slow model means reading the generated SQL and then reading the warehouse's query plan, exactly as you would for a query you wrote by hand.

Refs, and where the graph comes from

The one genuinely clever thing in the design is that a model does not name its inputs with table names. It names them with a function call that resolves to a table name at compile time. A model that says it selects from a reference to the staging orders model is, after compilation, selecting from a fully qualified table in a particular schema, and which schema depends on whether you are running in production, in your own development target, or in a temporary schema for a pull request.

The dependency graph falls out of this for free. The tool parses every model, collects every reference, and now knows that this model depends on that one. Nobody declares an edge. There is no configuration file listing the order. The order is derived from what the SQL actually reads, which means it cannot drift from reality, because the thing that reads the table is the same text that declares the dependency.

That is worth comparing against the orchestrator problem of undeclared edges. Inside one transformation project the problem does not exist, because an edge you forgot to declare is an edge you also forgot to read from, and your model would fail. The problem comes back the moment a model reads a table directly instead of through a reference, which is why every team of any size has a lint rule that forbids a raw table name in a select.

Templating is the smallest part

These tools ship with a templating language, and the templating language gets most of the attention in tutorials and almost none of the value. Loops that generate thirty near identical case expressions are impressive the first time and unreadable forever after. The templating features that actually earn their place are narrow. There is the reference function, which builds the graph. There is a variable for the current target, so a model can behave differently in development. There is a conditional that asks whether this run is an incremental one, which is the subject of most of this lesson. Beyond those, a macro is justified when the same non obvious expression appears in more than three models and would otherwise be copied.

The failure mode is a project where the SQL is no longer readable as SQL. If a reviewer cannot tell what columns a model produces without mentally executing a template, the template has cost more than it saved. A good instinct is that compiled output should be shorter and simpler than the source, not an expansion of it.

Materialisation is the real abstraction

The abstraction worth paying for is that a model is a select statement and the tool decides how to persist it. The same file, unchanged, can become a view, a table rebuilt from scratch on every run, a table that only new rows are added to, or a common table expression inlined into whatever reads it. You change one line of configuration and the physical strategy changes while the logic stays identical.

This is a genuinely useful separation, because the right physical strategy depends on things that have nothing to do with the logic. How often is this read. How much new data arrives. How expensive is a full scan. Is the upstream data immutable. Those answers change over a model's life, and when they change you want to change the strategy without touching the business rules, because touching the business rules means re reviewing them.

The estate, in numbers

The project this lesson draws on has four hundred and twelve models owned by eleven teams. Two hundred and four are tables rebuilt in full, ninety six are views, sixty four are inlined into their consumers rather than built at all, forty one are incremental, and seven are snapshots. A build of the entire estate from nothing takes two hours and thirty eight minutes. The nightly production run, which rebuilds the tables and appends to the incremental models, takes forty one minutes.

The gap between those two numbers is the subject of this lesson. Forty one minutes is a platform people trust, because it finishes before anyone looks at a dashboard. Two hours and thirty eight minutes is a platform people route around, because by the time it finishes the morning has been decided without it. The only reason the nightly run is forty one minutes is that the forty one incremental models, which are ten percent of the estate, hold almost all of the data.

What the tool does not do

It does not schedule itself. Something has to invoke it, which in practice is the orchestrator from the previous lesson, and the relationship between the two is a source of real design decisions. Does the orchestrator run the whole project as one task, in which case a failure in model three hundred takes the whole thing down and the retry redoes everything. Does it run each model as its own task, in which case you have duplicated the dependency graph into the orchestrator and the two can disagree. Most teams land in the middle, running the project in a handful of tasks split along ownership or freshness boundaries, which keeps the retry unit small enough without copying four hundred edges into another system.

It does not do data quality in any deep sense either. It runs assertions that you wrote, after the data has already been written, which is late but not useless. And it does not manage the warehouse. Clustering keys, partition layout, warehouse sizing and concurrency limits are all outside the tool and all of them affect your build time more than anything inside it.

Where the tool's own time goes

It is worth knowing the one place the tool does spend your time, because it surprises people. Before running anything, it parses all four hundred and twelve model files, resolves every reference, builds the graph, and then asks the warehouse what already exists so it can decide whether a table needs creating or replacing. On this project that preamble takes nine seconds of parsing and about twenty five seconds of metadata queries, so roughly half a minute passes before the first model runs.

Half a minute against a forty one minute build is nothing, and it is also the reason a developer running one model waits thirty five seconds for a six second query. The fix is not to make the tool faster. It is to know which part of the wait is fixed cost, because a developer who thinks a six second model takes thirty five seconds will start avoiding the tool and running statements by hand, and then the project and the warehouse disagree about what exists.

The other thing worth knowing is that the graph is built from the files on disk, not from what is in the warehouse. A model you deleted yesterday leaves its table behind, because nothing instructs the warehouse to drop it. Estates accumulate orphaned tables this way, and the only cure is a periodic reconciliation of what the warehouse holds against what the project declares. On this project that sweep found thirty one abandoned tables holding two hundred and sixty gigabytes.

What you own instead

What the tool gives you is a graph of SQL, built in order, with tests. What you own is every decision the tool deliberately refuses to make for you. Which models should be incremental. What the incremental predicate should be. How far back to look for late data. Whether to merge or replace partitions. Which models deserve to be views and which do not. How deep the graph is allowed to get. Which tests block a build and which only warn.

Every one of those is a decision with a measurable cost, and the rest of this lesson is about making them with numbers rather than defaults.

The tool compiles references into a graph, wraps each select in a materialisation, runs them in order and then asserts. Every number that decides what your platform costs sits in the column it does not fill in.
Key Insight