Data Engineering and DevOps
Transformation code deploys like software. The tables it already wrote do not, and that asymmetry is the whole lesson.
Who needs this, what one row is, and why the obvious build breaks
Every lesson starts from the consumer, because designing from the source outward is this domain's characteristic mistake.
What does it mean to deploy, version and roll back a change when the artefact is not a service but a table full of history?
Two people. The analyst who will read the table five minutes after your deploy and has no way to know a deploy happened. And the engineer who reverts your pull request at 3am expecting the numbers to go back to what they were, and finds that they do not (Rolling Back Data).
The unit being deployed is one model definition plus the range of data it has already materialised. Software delivery reasons about the first half only; a data change is not delivered until the second half has been recomputed, and until then the definition and the data disagree.
Treat the transformation project like any other repository: pull requests, CI that runs the tests, merge to main, deploy on green, revert if something breaks. This is a genuine improvement over scheduled SQL scripts on a server and it is the right starting point for every data team.
A change to the revenue model ships, runs overnight, and rewrites ninety days of fct_orders. The pull request is reverted the next morning. The code is correct again and the table still holds ninety days of the wrong numbers, because nothing re-ran (Backfills).
- A change to the revenue model ships, runs overnight, and rewrites ninety days of
fct_orders. The pull request is reverted the next morning. The code is correct again and the table still holds ninety days of the wrong numbers, because nothing re-ran (Backfills). - CI runs the model's tests against a tiny seeded fixture. Everything passes. In production the same SQL meets a real dimension with duplicate keys and fans out every fact row (Data Tests, Grain: What Does One Row Represent?).
- Two engineers merge on the same afternoon. Both models run in the same scheduled invocation, one reads the other mid-write, and the resulting table is a mixture of two versions that never existed together (Atomic Publish).
- A column is renamed in a model. The models downstream are updated in the same commit; the six BI dashboards, the two notebooks and the reverse-ETL sync that referenced the old name are not, because nothing in CI knows they exist (Impact Analysis).
- The environment promotion story is "point the same project at the production warehouse", so there is no way to run a real change against real data without running it in production (Development, Staging and Production).
- A schema migration is applied to a serving table while a scheduled job is halfway through writing it, and the job fails in a way that leaves the table neither in the old shape nor the new (Breaking Schema Changes).
What is actually happening
- A data deployment has two artefacts, not one. The definition — SQL, Python, a schema, a DAG — behaves exactly like software and can be versioned, reviewed, tested and reverted. The materialisation — the rows already written — is state, and reverting the definition does not touch it (Rolling Back Data).
- That is why a data rollback is a backfill. Restoring correctness means recomputing the affected range with the restored logic, validating it, and publishing it atomically. It is a data operation with a cost proportional to history, not a deploy (Planning a Backfill, Validating a Backfill Before You Publish).
- CI for data has to test two different things. The code — does the SQL compile, does the DAG resolve, is the model's contract unchanged — is testable in isolation and fast. The data — do the assertions hold against realistic input — is only testable against data, and a fixture small enough to be fast is usually too clean to be informative (Data Tests).
- Schema versioning is a producer-consumer problem, not a repository problem. A migration is safe or unsafe depending on who reads the column, and the repository does not contain the readers. This is why contracts and lineage do the work that a version number cannot (Data Contracts, Column-Level Lineage).
- Infrastructure-as-code covers the platform, not the data. Recreating buckets, roles, warehouses and schedules from code is genuinely achievable and genuinely valuable; recreating the contents from code is only possible if raw is retained and transformations are deterministic (Infrastructure as Code, Keeping Raw History: The Recovery Position and the Liability).
- Blue-green has a data analogue and it is the strongest deployment pattern in this domain. Write the new version to a separate location, validate it there, and swap the pointer that consumers read. That is what atomic publish is, and it makes the rollback a second pointer swap rather than a recompute (Atomic Publish).
- Idempotency is the property that makes any of this safe to retry. A deploy that half-ran can be re-run only if re-running produces the same result; without that, every failed deployment becomes a manual reconciliation (Idempotent Data Pipelines).
Where the boundary sits
DevOps and Production Engineering ask how software is built, tested, delivered and operated. This domain asks the same questions about an artefact that carries state — where "deployed" means a definition changed *and* history may need rewriting to match it.
Almost every delivery practice transfers: version control, review, CI, environments, immutable artefacts, progressive rollout, deploy markers. What does not transfer is the assumption underneath rollback — that reverting the artefact returns the system to its previous behaviour. For a stateless service that is true. For a model that has already written ninety days of rows, it is false in a way that is easy to miss because everything else looks the same.
So the table below has an unusual third column. Read it as: here is the practice, here is where its depth lives, and here is the specific way the practice changes when the artefact has already materialised.
| We teach | Depth lives in | The mechanism that crosses |
|---|---|---|
| Versioning a transformation project | DevOps: version control, trunk-based development, code review | The definition versions cleanly. The tables it produced carry no version unless you write one into them (Metadata: Technical, Operational and Business). |
| CI for data models | DevOps: pipeline design, test pyramids, build caching | Compilation and contract checks are fast and complete; correctness checks need realistic data, and a fixture clean enough to be fast is usually too clean to catch anything (Data Tests). |
| Deploying a pipeline | DevOps: artefact promotion, environment parity, progressive rollout (Build Once, Promote the Same Bytes, Four Ways to Replace Running Code) | Deploying the code is instant; the change is not in effect until the next run, and not complete until any affected history is recomputed (Backfills). |
| Rolling back | DevOps: rollback strategy, blue-green, canary (Blue/Green: Two Environments, One Switch, Canary: Let 5% of Traffic Find the Bug) | Reverting the code does not revert the tables. A data rollback is a bounded, validated recompute, or a snapshot restore if the table format offers one (Rolling Back Data). |
| Versioning schemas | DevOps: database migrations, expand-and-contract (Expand and Contract Migrations) | A migration's safety depends on the readers, and the readers are not in the repository. Lineage is what closes that gap (Column-Level Lineage). |
| Infrastructure as code | Cloud & DevOps: Infrastructure as Code, State: The File That Makes It Work and the File That Will Hurt You, Reading a Plan Before You Apply It | Buckets, roles, warehouses and schedules are reproducible from code. The datasets inside them are reproducible only from retained raw plus deterministic logic (Keeping Raw History: The Recovery Position and the Liability). |
| Environments | DevOps: environment strategy, seeding, ephemeral environments (Development, Staging and Production) | A realistic environment needs realistic data, and realistic data is production data with a governance obligation attached (PII in Pipelines). |
| Knowing a deploy caused it | Observability & Performance: "What Changed?" — Deploy Markers and the Invisible Deploys, Deploys Are the First Suspect | Overlay deploy markers on freshness and quality charts, not only on latency charts. A data anomaly is a deploy suspect too. |
The revert that did not revert anything
This is the sharpest point on the boundary and it is worth stating in one sentence: a code rollback restores what will be computed next, and changes nothing about what was computed already.
The reason it catches experienced engineers is that every signal says the incident is over. The pull request is reverted, the deploy is green, the scheduled run succeeds, the tests pass. The only evidence that anything is still wrong is the numbers, and the numbers are exactly what nobody re-checks after a revert.
The comparison below is the correct shape of a data change. Note that the better version is not more cautious in some vague way — it is structurally different: the new output is produced somewhere consumers are not reading, compared against the old output over the same input, and only then made visible by a swap that is instant and reversible.
The pull request changes the revenue logic and merges. The scheduled run rewrites ninety days of `fct_orders` in place. A stakeholder notices the numbers moved. The pull request is reverted, the next run succeeds, and the table still holds ninety days computed with the reverted logic — with no marker anywhere saying which rows came from which definition.
The changed model writes to a new location over the same input range. An automated comparison reports row counts, aggregate deltas and per-key differences against the current table, and a human approves the diff. A pointer or view swap makes the new version visible atomically. The previous version stays materialised until its retention passes, so rollback is a second swap.
The artefact being deployed is state, not behaviour. Reverting a definition cannot un-write rows, so the only way to make rollback cheap is to never destroy the previous materialisation in the first place — which turns both the deploy and the rollback into a metadata operation whose cost is independent of how much history the model covers (Atomic Publish, Rolling Back Data).
What is actually versioned, and what is not
A useful exercise for any data team: draw the line through your own repository between the things a git revert restores and the things it does not. It is almost always further left than people expect.
The tree below is a typical transformation project. Everything above the line behaves like software. Everything below it is state that happens to be described by the things above it, and it changes only when something runs.
The practical consequence is a review question worth asking on every pull request that touches a model: *if this is wrong, what has to be recomputed, over what range, and how long does that take?* A change whose answer is "nothing, it is additive" is a normal deploy. A change whose answer is "ninety days of the largest fact table" is a data migration wearing a pull request's clothes (Planning a Backfill).
transform/ <- versioned; `git revert` restores all of this
models/
staging/stg_orders.sql definition of a transformation
marts/fct_orders.sql definition, plus its materialisation strategy
schemas/
fct_orders.yml declared columns, types, tests, owner
metrics/
revenue.yml the agreed expression of a business metric
macros/ tests/ seeds/
dbt_project.yml / dag.py wiring, schedules, dependencies
.github/workflows/ci.yml what runs on a pull request
infra/ buckets, roles, warehouse, schedules (IaC)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
everything below is STATE. `git revert` does not touch any of it.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
warehouse/
analytics.fct_orders 90 days of rows written by the OLD definition
analytics.dim_customer SCD2 history that cannot be recomputed if
the source overwrote the states it records
lake/raw/orders/dt=.../ the only input a recompute can start from
broker/orders.v1 replayable ONLY within its retention window
bi/ dashboards, notebooks, extracts, reverse-ETL
syncs — consumers the repository cannot seeHow to build it
Most important first.
- Publish every model to a new location and swap a pointer or a view, rather than writing in place. It converts both deployment and rollback into a metadata operation and removes the window where consumers read a partial state (Atomic Publish).
- Make the rollback plan part of the change, not part of the incident. Every pull request that changes a model's output should say which range needs recomputing if it turns out to be wrong (Planning a Backfill).
- Run the change against real production data in an isolated output location before promoting it. Comparing old output with new output over the same input is the single most informative data test there is (Validating a Backfill Before You Publish).
- Enforce contracts in CI: the model's output schema, its declared grain, and the columns downstream consumers depend on. That is the only part of the blast radius the repository can actually see (Contract Enforcement).
- Version schemas and metric definitions as deliberately as code, with the version recorded in the produced data so a row can say which definition made it (The Metrics Layer, Schema Evolution).
- Treat the platform — buckets, roles, warehouses, schedules — as code so an environment is reproducible, and be explicit that this does not make the data reproducible (Infrastructure as Code, State: The File That Makes It Work and the File That Will Hurt You).
What this actually promises
Naming the guarantee you do not have is worth more than naming the one you do — everything downstream inherits the weakest promise in the chain.
- A merged pull request guarantees that the definition in the repository is the intended one, from that moment forward. It guarantees nothing about any data already produced by the previous definition.
- A green CI run guarantees that the assertions you wrote hold against the input you tested against. On a synthetic fixture that is a statement about the fixture (Data Quality).
- An atomic publish guarantees that readers see either the old complete version or the new complete version, never a mixture — and only for the tables it covers, which is why cross-table consistency needs its own mechanism (Rolling Back Data).
- Infrastructure-as-code guarantees that the platform can be recreated. It explicitly does not guarantee that the datasets can be, and conflating the two is how disaster-recovery plans end up untested (Disaster Recovery).
Can I trust it?
A green pipeline is evidence that code ran. These four fields are the evidence that the data is right.
- The check that belongs on this boundary is an old-versus-new comparison over the same input range: run the changed model against production data into a scratch location and diff the aggregates, row counts and per-key values against the current table.
- It catches unintended fan-out, changed filters, casts that null, and grain changes — the categories of mistake that no unit test on a fixture will produce. It misses anything where the old output was already wrong, because it measures difference rather than correctness.
- It also misses consumers outside the repository. A diff that shows no change in the model can still break a dashboard that depended on a column's ordinal position or on a name that was refactored (Impact Analysis).
- A deployment that requires a backfill converts a code change into a data-latency event: the affected range is wrong or missing until the recompute finishes, and consumers should be told rather than left to notice (Data Incidents).
- Blue-green publication removes the window in which a deploy makes data temporarily unavailable, at the cost of holding two copies during the swap.
- The deploy frequency a data platform can sustain is bounded by how long a full recompute of the affected models takes, which is why incremental models are a delivery concern and not only a cost one (Incremental Processing).
- Additive changes — a new column, a new model — are safe to deploy without a backfill, because nothing existing changes meaning. They are the only category that behaves like a normal software deploy (Backward Compatibility).
- Changes to an existing column's logic require a backfill of every range a consumer might compare across, or the table contains two definitions with a seam in the middle and no marker at the seam (Semantic Changes).
- Removals require a deprecation window measured by consumer migration, not by sprint length, and lineage is what tells you when the window can close (Data Lineage).
- Restore the definition first — that is the fast part — and then recompute the affected range with it. Treat those as two steps with two owners, because the second one has a cost and a duration the first does not (Reprocessing vs Retrying).
- Recompute into a location consumers are not reading, validate, and then swap. Overwriting in place while people are reading is how a bad deploy becomes two incidents (Atomic Publish).
- If the table format supports snapshots, a rollback can be a metadata operation rather than a recompute — which is one of the strongest practical arguments for a table format over bare files (Open Table Formats).
- Bound the recompute explicitly by range and validate before publishing. An unbounded "just re-run everything" is how a fix for one week corrupts a year (What Backfills Break).
What can go wrong
- A revert that restores the code and leaves the data wrong — the characteristic failure of this boundary and the reason the lesson exists.
- CI that is green because the fixture is too clean to contain the shape that breaks the model.
- Two models deployed together, one reading the other mid-write, producing a mixture no version of the code would produce.
- A rename that is safe inside the repository and breaks every consumer outside it.
- The mitigation failing: an old-versus-new diff run against a stale copy of production, so it compares the new logic to data that is already three days behind and attributes the difference to the change.
- A backfill launched to repair a deploy, running unbounded, and overwriting correct history alongside the incorrect range (What Backfills Break).
- "We reverted the change, so we are fine." The definition is restored and the materialised rows are not. Until the affected range is recomputed and validated, the data still reflects the reverted logic.
- "CI passed, so the model is correct." CI evaluated your assertions against your fixture. Data correctness is a property of production input, which CI has not seen (The Pipeline Succeeded. The Data Is Wrong.).
- "Infrastructure-as-code means we can rebuild everything." It means you can rebuild the platform. Rebuilding the data requires retained raw input and deterministic transformations, which are separate decisions (Keeping Raw History: The Recovery Position and the Liability).
- "A backfill is a deployment step." A backfill is a data operation with a range, a validation gate and a publish strategy. Automating it as a post-deploy hook is how a bad change gets applied to history faster (What Backfills Break).
- "Feature flags let us roll back instantly." They let you switch which logic runs next. Rows already written under the other branch stay written, and now the table contains both (Feature Flags: Rollout, Kill Switches and Debt).
Operating it
- Deploy markers on every data-quality and freshness chart. A data anomaly that begins within one scheduled interval of a deploy has a very short suspect list ("What Changed?" — Deploy Markers and the Invisible Deploys, Deploys Are the First Suspect).
- The model version, code revision and run identifier recorded as columns or table properties on the output, so a row can say which definition produced it (Metadata: Technical, Operational and Business).
- Time-to-recompute per model, tracked, because it is the real bound on how quickly a bad change can be undone (Pipeline SLOs).
- A count of consumers per model from lineage, surfaced in the pull request, so blast radius is visible at review time rather than at incident time (Impact Analysis).
- At 10x history, full recomputes stop fitting inside a deploy window, and the delivery process has to become range-bounded and incremental to stay usable (Full Refresh vs Incremental).
- At 100x models, or many teams in one repository, the binding constraint is the dependency graph: a change to a base model triggers recomputation of everything downstream, and nobody can see that from the diff (The Transformation DAG).
- Consumer count scales the coordination cost of any change, not the technical cost. Ten consumers can be emailed; two hundred need contracts and a deprecation process (Data Contracts).
- The dominant cost of a data deployment is not the deploy, it is the recompute it implies — proportional to the history being rewritten, and paid every time a definition changes (Compute Waste).
- Blue-green publication costs a duplicate copy of each table for the duration of the swap, which is real and usually cheap relative to the incident it prevents.
- Testing against realistic data costs compute on every pull request, and is the cost most often cut first and regretted most reliably.
- Blue-green publication buys instant rollback and costs storage plus a more complex write path. For a large table rewritten daily this is an excellent trade; for a small append-only log it is ceremony.
- Testing against production-like data buys real signal and costs compute, time on every pull request, and a governance problem — because a realistic fixture of customer data is customer data (PII in Pipelines).
- Contract enforcement makes breakage loud, which means more changes are refused at the boundary and delivery is slower. That is the point, and it is still a cost.
Where this applies
Almost nothing here is universal. These labels say what each claim is specific to, and where a different engine, format, warehouse or scale would differ.
- GENERALThat a definition and its materialisation are separate artefacts, and that reverting one does not revert the other, is true of any transformation framework and any storage layer.
- TOOL-SPECIFICFrameworks differ in how much of this they do for you: some materialise into a temporary relation and swap, some write in place, some maintain snapshot history that makes rollback a metadata operation. Whether your rollback is a pointer swap or a recompute depends on that choice, so verify it before you rely on it.
- ORG-SPECIFICWhether a data team can require review, CI and contracts depends on whether analysts write models in the same repository or in the BI tool. Where transformation happens inside a dashboard, none of this delivery discipline is available at all, which is itself the strongest argument for moving it out.
Where the depth lives
This domain teaches how data moves and how you know it arrived intact. It hands the rest off by name.
- — DevOps / Production Engineering does not exist in Engineer Atlas yet. When it lands it owns the depth on: continuous integration and build pipelines, artefact immutability and promotion between environments, deployment strategies and progressive rollout, configuration and secret delivery, infrastructure-as-code workflow, incident command, and on-call practice.
- — What this domain adds and that domain will not: the deployed artefact has already written state. Every rollback story in software delivery assumes reverting the artefact reverts the behaviour, and for a materialised data model that assumption is false. A data rollback is a bounded, validated recompute — or a snapshot restore, where the table format provides one.
- — The second thing that does not transfer: the blast radius of a change is not in the repository. Dashboards, notebooks, extracts and reverse-ETL syncs consume these tables and appear in no dependency file, which is why lineage does the job that a build graph does for software.