Backstage · study group talk

From notebook
to production

What a data engineer actually does, on Databricks (and a little Fabric)

Magnus Vinjebo · Backstage

Context

Who I am, and what Backstage does

Me

  • MSc from DTU, ML background (thesis on concept drift in streaming data)
  • Now building data platforms for clients at Backstage
  • Same Python and Spark, very different questions

Backstage

AI data consultancy in Copenhagen, built on Databricks. Three kinds of work:

Data platforms

Lakehouses that ingest, clean and model company data

AI & agents

Models and agents that run on top of that data

Insight

Dashboards and answers for the business

The hook

Where did your dataset come from?

train.csv what an ML project usually starts from 5 source systems, 5 ID formats Deduplication & quality checks Incremental & late-arriving rows Definitions: what is a sale? History: what did we know, when? Joins & conformed keys Schedules, retries, alerts Access control, PII, lineage
The big picture

The data engineering workflow

1 · Sources

ERP, CRM, APIs, files, events

systems of record

2 · Ingest

Copy data in, reliably and incrementally

Lakeflow ConnectAuto Loader

3 · Store

Open tables in the lakehouse

Delta Lake

4 · Transform

Clean, join, model: bronze → silver → gold

Lakeflow Pipelines

5 · Serve

BI, ML features, AI agents

SQL · MLflow · Genie

This strip comes back in the top-right corner on the next slides, so you can see where we are.

Sources & ingestion

Getting data in, without breaking things

ERP (SAP…)CRMSQL databasesREST APIsExcel on SharePointEvent streams / IoT
  • Batch vs. streaming: nightly dump or continuous? Most business data is fine with batch every hour or day.
  • Full vs. incremental: re-copying 200 M rows every night doesn't scale. Load only what changed (watermarks, CDC = change data capture).
  • Idempotent: running the same load twice gives the same result. Things will fail and be rerun.
  • Schema drift: the source adds a column on a Tuesday. Decide upfront: evolve, rescue or fail.

Auto Loader: pick up only new files, remember progress

# files land in a Unity Catalog volume
(spark.readStream.format("cloudFiles")
   .option("cloudFiles.format", "json")
   .option("cloudFiles.schemaLocation", chk)
   .load("/Volumes/raw/sap/orders/")
 .writeStream
   .option("checkpointLocation", chk)
   .trigger(availableNow=True)
   .toTable("main.bronze.sap_orders"))

For SaaS apps and databases, Lakeflow Connect gives managed connectors (e.g. Salesforce, SQL Server, SharePoint) with CDC built in.

Store

The lakehouse: Parquet + a transaction log

main.silver.orders/ data files part-0001.parquet part-0002.parquet part-0003.parquet part-0004.parquet … _delta_log/ 000.json create 001.json append 002.json merge 003.json delete Each commit lists which files make up the table at that version.
  • ACID transactions: a failed job never leaves half-written data
  • Schema enforcement: a string in an int column is rejected, not silently cast
  • MERGE / upserts: apply updates and deletes from CDC
  • Time travel: query any earlier version
SELECT * FROM main.silver.orders VERSION AS OF 12;
DESCRIBE HISTORY main.silver.orders;

ML link Log the table version with your model run, and you can reproduce the exact training data months later.

Transform

Medallion architecture

ERP CRM Files / API Bronze raw, as delivered {"ID":"0042","AMT":"12,50","CUR":"dkk"} · append-only· full history· replayable Silver cleaned & conformed order_id42amount_eur1.68customerC-104 · typed & deduplicated· one ID per customer· quality checked Gold business-ready weeksegmentsales2026-41SMB1.2M2026-41Ent.3.4M · facts & dimensions· business names· aggregates, KPIs BI ML AI agents

main.bronze.sap_orders → main.silver.orders → main.gold.fct_sales

Transform & data quality

Declare what the data should look like

In a notebook

# orders_cleaning_FINAL_v3.ipynb
df = spark.read.csv("orders.csv")
df = df.dropna()
df = df.filter("amount > 0")
df.write.parquet("clean_orders")
# how many rows did we drop? why?

Works once. Silent about what it threw away.

In a Lakeflow declarative pipeline

from pyspark import pipelines as dp
from pyspark.sql import functions as F

@dp.materialized_view(comment="One row per order line")
@dp.expect_or_drop("valid_amount", "amount > 0")
@dp.expect_or_fail("has_id", "order_id IS NOT NULL")
def silver_orders():
    return (spark.read.table("bronze.sap_orders")
        .dropDuplicates(["order_id", "line_no"])
        .withColumn("amount",
            F.col("amount").cast("decimal(18,2)")))

expect keep the row, record the violation

expect_or_drop drop it, and count it

expect_or_fail stop the update

Data modeling

Star schemas, and why ML should care

dim_customer dim_product dim_date dim_store fct_sales one row per order line

Facts = events you count (sales, clicks). Dimensions = the context you slice by (who, what, when, where).

SCD type 2 (slowly changing dimension): keep every version of a row.

customersegmentvalid_fromvalid_to
C-104SMB2024-01-012025-06-30
C-104Enterprise2025-07-019999-12-31

ML link: point-in-time joins. Predicting 2024 churn with today's segment = leaking the future. Join the version that was valid at the label date.

The mindset shift

Notebook vs. production pipeline

One-off notebookProduction pipeline
InputsHardcoded paths, "final_v3.csv"Parameterised: environment, dates, catalog
RerunsRun cells top to bottom and hopeIdempotent: rerun any day, same result
CorrectnessEyeball df.head()Unit tests + data expectations
RunningYou press Shift+EnterScheduled or triggered, with retries
FailureSomeone notices weeks laterAlert within minutes, with lineage
ChangeEdit in placeGit → PR → review → CI → deploy

Notebooks are still great for exploring. The work is turning the result into something that runs without you.

Orchestrate & monitor

Lakeflow Jobs: what runs, when, in which order

ingest_sap ingest_web medallion_pipeline train_forecast failed → retry 1/3 succeeded on retry 1 refresh_bi notify_teams

Triggers

Cron schedule, file arrival, table update, or continuous

When it breaks

Retries, timeouts, and repair run: rerun only the failed tasks

Watching it

Run history, durations, alerts to email or Teams/Slack, cost per job

Govern

Unity Catalog: who, what, where from

prod.sales_gold.fct_sales

catalog . schema . table / view / volume / model / function

-- access control in plain SQL
GRANT SELECT ON SCHEMA prod.sales_gold TO `analysts`;

-- tag sensitive columns
ALTER TABLE prod.silver.customers
  ALTER COLUMN email SET TAGS ('pii' = 'email');

Lineage, captured automatically

bronze.sap_orders silver.orders gold.fct_sales Power BI report forecast model

"If I change this column, what breaks?" is now a click, down to column level.

Deploy

Pipelines are software: git, tests, environments

feature branch→ pull request + CI tests→ dev→ test→ prod

Declarative Automation Bundles

Formerly Databricks Asset Bundles ("DABs"). Jobs, pipelines, permissions and clusters as YAML in the repo, deployed with one command.

databricks bundle deploy -t prod

CI (GitHub Actions / Azure DevOps) runs pytest on transformation logic, then deploys. Nobody clicks around in prod.

# databricks.yml
bundle:
  name: sales_platform
include:
  - resources/*.yml        # jobs + pipelines
targets:
  dev:
    mode: development       # prefixed, paused
    default: true
  prod:
    mode: production
    workspace:
      host: https://adb-prod.azuredatabricks.net
Serve

Who uses the gold layer?

BI & analytics

  • SQL warehouses (serverless)
  • Power BI on top of gold tables
  • AI/BI dashboards in Databricks
  • Metric definitions in one place

Machine learning

  • Feature tables in Unity Catalog
  • MLflow: experiments, registry
  • Batch scoring as a job task
  • Model serving endpoints

AI & agents

  • Genie: ask the data questions in plain language
  • Vector search over documents
  • Agents that call governed tables and tools

Same tables, same permissions, same lineage, whatever the consumer. An agent is only as good as the gold layer it reads.

The other big platform

Microsoft Fabric, in one slide

SaaS analytics inside the Microsoft / Power BI world. Every workload reads and writes OneLake, one Delta lake per tenant.

Data FactoryLakehouse + SparkWarehouse (T-SQL)Real-Time IntelligenceData SciencePower BI · Direct Lake

Both ways Fabric can mirror a Databricks Unity Catalog, so Power BI reads gold tables without copying them.

DatabricksFabric
ModelPlatform in your cloud (Azure, AWS, GCP)SaaS in your Microsoft tenant
StorageDelta in your storage + Unity CatalogDelta in OneLake
BillingPay per compute used (DBUs)Fixed capacity (F‑SKUs)
StrengthHeavy engineering, ML/AI, scalePower BI-centric teams, low-code
Putting it together · anonymised example

"How much will we sell next quarter?"

Sources

  • SAP sales orders
  • Product master (SQL Server)
  • Web shop events
  • Holiday calendar API

Ingest

  • Lakeflow Connect for SQL Server (CDC)
  • Auto Loader on SAP exports
  • Daily, incremental

Transform

  • Dedup, currency to EUR
  • Product hierarchy as SCD2
  • Weekly sales fact in gold

Serve

  • Forecast model (MLflow) writes gold.forecast
  • Power BI report
  • Genie space for sales

Where the time actually went: agreeing on what "a sale" means (returns? internal orders? which date?) and getting history right. Not the model.

Takeaways

If you remember four things

01 The dataset is a product

Someone owns it, versions it, documents it and is paged when it breaks.

02 Pipelines, not notebooks

Idempotent, parameterised, tested, scheduled. Explore in notebooks, ship as pipelines.

03 Quality & lineage are features

Expectations, Unity Catalog, time travel: measured, not assumed.

04 ML ↔ DE is one team

Point-in-time features, versioned training data, models as job tasks.

Thanks

Questions?

Magnus Vinjebo · Backstage