Week 13 - Big Data on Databricks

The lakehouse idea

Workspace & Unity Catalog

PySpark in Databricks

dbt on Databricks

Scheduling dbt Jobs

Practice

Assignment

Gotchas & Pitfalls

Slides (PDF)

Career relevance: Week 13

Glossary: Week 13

History of the Lakehouse

PySpark in Databricks

In Workspace & Unity Catalog you found hyf.nyc_yellow and ran SQL on a warehouse. This chapter is the other common path on Databricks: a notebook (interactive Python or SQL cells under Workspace) attached to a cluster, writing PySpark (the Python API for distributed DataFrame work). PySpark runs on Apache Spark (the open-source engine on that cluster).

By the end of this chapter, you should be able to:

The notebook

A notebook is an interactive document of cells: you type code in a cell, run that cell only, and the result appears under it. Variables stay in memory for the next cell. On Databricks, a cell can run Python (including PySpark) or SQL. You pick the language per cell (or start a cell with %sql). You will try both: first a SQL cell on the warehouse you already know, then Python on a cluster. Try the cell idea before the theory:

https://lasse.be/simple-hyf-teach-widget/notebook-cells.html

Run the three cells in order (cell 3 reuses trips from cell 2). First load can take a moment while the browser starts Python. That is the interactive loop you will use on Databricks next.

So far this track you have mostly run Python as a script: a .py file that starts at the top, runs to the end, and exits. A notebook is different on purpose: like a long-lived Python session, the same idea as a REPL (Read-Eval-Print Loop: type a bit of code, run it, see the result, keep the variables and continue). Load raw_trips once, then try filters and aggregations many times without reloading.

Regular Python (.py script) Notebook
How you run it Top to bottom in one go One cell at a time (any order you click)
After it finishes Process exits; memory is cleared Session stays up; variables stay in memory
Change one line Usually re-run the whole file Re-run that cell (and any cells that depend on it)
Output Terminal print / logs Result under the cell you ran
Good for Fixed pipelines, apps, jobs Exploration, demos, trying filters many times
Stale-state risk Low (each run starts fresh) High if you edit a cell and forget to re-run it

That cell model comes from Jupyter Notebooks (files ending in .ipynb). Databricks notebooks use the same idea in the browser. You do not install Jupyter for class work this week; you create a Databricks notebook under Workspace.

Notebooks shine at interactive exploration. They are a weak place to ship a long-running product. On Databricks, many data scientists prefer notebooks for exploration; as a data engineer, you will often inherit them and later turn working notebooks into reliable Python (modules, jobs, schedules). This chapter teaches you to run notebooks safely. Migrating them into applications (and proving transforms with local pytest fixtures) is optional depth on Going Further.

<aside> ⚠️ Cell order is the order you ran, not the order on screen. The notebook remembers variables from every cell you executed, even if those cells sit below the one you are reading. Example: cell 1 sets trips = 3, cell 2 prints trips * 2. You run both and see 6. Later you change cell 1 to trips = 10 but only re-run cell 2. The print still uses the old value. Before you trust a result, run top to bottom (or clear state and re-run).

</aside>

Create one: open Workspace, right-click a folder (or use + New), choose Notebook. Set the default language to Python. You should see an empty cell ready for code, with a compute dropdown at the top of the page.

Sidebar + New menu with Notebook selected to create a notebook

Sidebar + New menu with Notebook selected to create a notebook

New Python notebook: empty cell and compute dropdown in the toolbar

New Python notebook: empty cell and compute dropdown in the toolbar

Leave the notebook open. Cells will not run until you attach compute in the next sections. For SQL cells that can be the warehouse; for Python it must be a cluster. Once a cluster is attached, Databricks defines spark for you.

<aside> ⌨️ Hands on: Create a new Python notebook under Workspace. Look at the empty cell and the compute dropdown at the top. Do not run cells yet.

</aside>

Compute for notebooks

Nothing in a notebook runs without machines. Databricks gives you two kinds of compute this week:

flowchart TB
    subgraph cluster["cluster"]
        drv["driver<br/>one machine"] -->|sends tasks| ex["executors<br/>one or more machines"]
        ex -->|return partial results| drv
    end
    classDef box fill:#e8f0fe,stroke:#4285f4,color:#111;
    class drv,ex box;

A notebook is only a document of cells. It does not have its own CPU or memory. Attach means: connect this notebook to compute so each cell has somewhere to run.

<aside> 🤓 Curious Geek: Who else is in the Spark cluster?

The diagram shows the two roles you use in class: a driver (plans and coordinates) and executors (do the work). In full Spark deployments a cluster manager (YARN, Kubernetes, or Databricks Compute) allocates those machines. Optional depth: Spark cluster architecture.

</aside>

First: SQL in the notebook on the warehouse

At the top of the notebook, open the compute dropdown and attach hyf-dbt-warehouse (under serverless warehouses).

Attach to an existing compute resource: SQL Warehouse selected, hyf-dbt-warehouse, Attach

Attach to an existing compute resource: SQL Warehouse selected, hyf-dbt-warehouse, Attach

Set the cell language to SQL, or type %sql on the first line, then run:

SELECT count(*) AS trip_count
FROM hyf.nyc_yellow.raw_trips

Databricks notebook SQL cell: count(*) on raw_trips returning 128202548

Databricks notebook SQL cell: count(*) on raw_trips returning 128202548

You should see the same order of magnitude as in Catalog Explorer / the SQL editor (~128 million). That proves the notebook can run SQL when it is attached to the warehouse.

<aside> ⌨️ Hands on: Attach hyf-dbt-warehouse to your notebook. Run the count(*) SQL cell above. Confirm you get a large number on the order of 128 million.

</aside>

Then: switch to a cluster for Python

SQL on the warehouse worked. Python will not: the warehouse has no driver-plus-executors Spark session for PySpark cells. For the rest of this chapter you need the shared class cluster hyf-class-cluster.

Open the notebook compute dropdown again and attach hyf-class-cluster under General compute (not the warehouse). If the list shows another cluster (for example a teacher test cluster), ignore it: class PySpark work uses hyf-class-cluster. You do not need to open the Compute page for class work. Creating and inspecting clusters there is optional depth on Going Further.

Attach to an existing compute resource: General compute selected with hyf-class-cluster

Attach to an existing compute resource: General compute selected with hyf-class-cluster

If the cluster shows Terminated in the dropdown, start it from there (or ask your teacher if it is already warm). On the class subscription a cold start can take ten minutes or more and occasionally fails with a capacity error; details are in Gotchas.

When the status shows Connected (or similar), add a Python cell (hover between cells and click Code, or press b below a selected cell) and run:

Empty Python notebook cell ready for code

Empty Python notebook cell ready for code

print("hello from the cluster")

Databricks notebook Python cell: print hello from the cluster with matching output

Databricks notebook Python cell: print hello from the cluster with matching output

The first run can be slow if the cluster just woke up. If Python cells fail with messages about missing Spark or an unavailable session, the notebook is not attached to a cluster.

flowchart LR
    A["1. Create<br/>notebook"] --> B["2. SQL cell<br/>on warehouse"]
    B --> C["3. Attach<br/>cluster + Python cell"]
    classDef box fill:#e8f0fe,stroke:#4285f4,color:#111;
    class A,B,C box;

<aside> ⌨️ Hands on: Attach hyf-class-cluster to your notebook, and run print("hello from the cluster").

</aside>

Why not stay on the warehouse for everything? Capability first: the warehouse runs SQL (editor, dbt, and SQL notebook cells). Python and PySpark need a cluster. Cost second: the warehouse wins for short SQL (idle scales to zero); a warm cluster wins for interactive PySpark once it is running, but idle minutes still cost money until auto-termination. When you finish a hands-on, let the cluster auto-terminate (usually about 20 minutes of idle time on the class cluster). Treat that as a backup, not a reason to leave the cluster up while you take a long break.

<aside> 💡 The notebook compute dropdown may also list Serverless. That is not the SQL warehouse: it is Databricks-managed Spark for notebooks (docs). The required path this week is still the shared classic cluster so you see driver plus executors. Optional depth on Going Further.

</aside>

Apache Spark

The lakehouse idea made the case for distributed computing: when data outgrows one machine, you spread the work. Apache Spark is the concrete open-source engine that does that. Databricks was founded by Spark's creators and hosts Spark (and more) as a managed platform. Keep four names separate:

Name What it is What you do with it this week
Python The language in the notebook cell print(...), imports, control flow
Apache Spark The distributed engine (open source) Understand it: driver + executors run your job
PySpark The Python API for Spark Write notebook cells: spark.read, filter, groupBy
Databricks The managed platform around Spark Use the workspace, Unity Catalog, Delta tables, and hyf-class-cluster

Plain Python runs in one process. PySpark looks like Python DataFrame code, but the heavy work runs across the cluster. Your notebook is a thin client: Python cells call PySpark; PySpark asks Spark to plan and run the job on the same driver and executors you attached above. You do not install Spark on your laptop for class.

flowchart TB
    subgraph dbx["Databricks platform"]
        subgraph spark["Apache Spark engine"]
            pys["PySpark<br/>Python API you write"]
        end
        extras["Also: SQL warehouse, Jobs,<br/>Unity Catalog, notebooks, …"]
    end
    you["Your notebook cell"] --> pys
    pys -->|"plans and runs the job"| spark
    classDef box fill:#e8f0fe,stroke:#4285f4,color:#111;
    class you,pys,extras box;

You write PySpark. Spark does the distributed work on the cluster. Databricks is the workspace that hosts Spark (and more than Spark).

For day-to-day work you are usually a consumer of a shared cluster: attach it, run cells, keep results small. For a 56K-row mart on one Postgres VM, Spark's startup overhead usually makes the job slower. This week's ~128 million trips are large enough that we run them on Databricks with Spark so you practice the multi-developer lakehouse path.

<aside> 💭 Single-machine engines such as DuckDB and Polars can often handle hundreds of millions of rows without a cluster. The comparison table in the next section spells out when those still win.

</aside>

Read first, then transform

Start with the smallest useful action: read a table and ask how many rows it has. That proves the catalog name, the cluster, and the spark session all work before you add joins.

flowchart LR
    read["spark.read.table<br/>hyf.nyc_yellow.raw_trips"] --> df["DataFrame<br/>trips"]
    df --> count["count()<br/>action"]
    df --> show["filter + show(5)<br/>action"]
    count --> n["one integer<br/>~128M"]
    show --> sample["five sample rows"]
    classDef box fill:#e8f0fe,stroke:#4285f4,color:#111;
    class read,df,count,show,n,sample box;

The first line uses spark. That is Databricks' pre-created SparkSession (the Python entry point to Spark). You do not import or build it in a notebook; a laptop PySpark script would use SparkSession.builder.getOrCreate() instead. Helpers such as from pyspark.sql import functions as F you still import when you need them. The result of spark.read.table(...) is a DataFrame (PySpark) (a distributed table-like dataset on the cluster).

Type this in a notebook cell:

trips = spark.read.table("hyf.nyc_yellow.raw_trips")
zones = spark.read.table("hyf.nyc_yellow.raw_zones")

trips.printSchema()
print(trips.count())  # action: runs a job; expect roughly 128_000_000

After you run it, the notebook cell looks like this:

Databricks notebook cell: printSchema for raw_trips and count() returning 128202548

Databricks notebook cell: printSchema for raw_trips and count() returning 128202548

printSchema (print column names and types without scanning all rows) shows the schema without pulling row data to your laptop. count (return one integer row total) is an action (an operation that finally runs the Spark plan): executors scan the table; only the integer comes back to the driver.

Next, practice one transformation and one safe action. Transformations are lazy: they build a plan and do not run until an action needs a result (full explanation in the next section).

from pyspark.sql import functions as F

# Transformation: builds a plan, does not run yet
# F.col("payment_type") is a Column in that plan (not a Python bool).
# TLC: payment_type == 2 means cash.
cash_trips = trips.filter(F.col("payment_type") == 2)

# Action: runs the plan and prints a few sample rows
cash_trips.select("pickup_datetime", "total_amount", "payment_type").show(5)

After you run it, the notebook cell looks like this:

Databricks notebook cell: filter payment_type == 2 and show(5) sample rows

Databricks notebook cell: filter payment_type == 2 and show(5) sample rows

filter (keep rows that match a condition) and select (keep or rename columns) describe work. show (print a small sample of rows) is the action that finally runs them.

F is the usual short name for pyspark.sql.functions. F.col("payment_type") == 2 looks like ordinary Python, but it builds a Column expression for Spark's plan: compare the payment_type column to 2 later on the cluster. It is not a True/False value in your notebook process the way pandas df["payment_type"] == 2 is. On this TLC dataset, 2 means cash.

<aside> ⌨️ Hands on: Run both cells above. Use the screenshots as a guide for what success looks like: count() on the order of 128 million, and show(5) printing five rows without crashing.

</aside>

Why not pull 128M rows into pandas on your laptop?