Module · 5 lessons

Spark SQL & Execution Plans

The DataFrame API and SQL are two front-ends to one engine — Catalyst — and this module teaches you to read what that engine decides. Write the zone-hour report as SQL and watch it compile to a byte-identical plan, read execution plans node by node, see column pruning and predicate pushdown as plan diffs, measure what a Python UDF costs the optimizer, and fix a deliberately slow query by reading its plan.

At a glance

Level
Intermediate
Lessons
5 lessons
Time to complete
1 week
Cost
Free forever · no sign-up

Welcome to Spark SQL & Execution Plans, the fourth module — where you learn that the API you spent Module 3 mastering was never the real thing. Under df.filter(...) and df.groupBy(...) sits Catalyst, Spark’s query optimizer, and the DataFrame API is only one of two ways to feed it. The other is SQL. Register a DataFrame as a temp view and the exact zone-hour report you built last module becomes a SQL string — and it compiles to a byte-identical physical plan, because both front-ends meet in the same engine. Neither is faster; they are two dialects for describing the same work, and fluent data engineers move between them by which reads more clearly for the task.

Once you see that both roads lead to one plan, the plan itself becomes the thing worth reading — and .explain() becomes this module’s primary instrument. A physical plan is not mysterious once you can name its parts: a FileScan at the leaves that already knows which columns it needs and which filters to push down, HashAggregate nodes that pre-aggregate on each partition before a Exchange shuffles the small partial results, a final aggregate, and the AdaptiveSparkPlan wrapper that rewrites the whole thing at runtime. You’ll learn to read a plan bottom-up, point at any node and say what it does, and — crucially — see Catalyst’s optimizations as concrete before-and-after diffs: nineteen columns pruned to three, a filter pushed into the scan. You’ll also meet pushdown’s honest limit, because the taxi files ship no statistics, so a pushed filter skips no data on them — and you’ll build a file where it does, to see the difference.

Then the module draws the optimizer’s boundary. A Python UDF is a black box Catalyst cannot see through: it can’t prune, push, or generate code across it, and every row pays to cross into Python and back. You’ll measure exactly what that costs against a built-in expression and a vectorized pandas_udf, and learn the ordering that follows. The guided project puts it all together on a deliberately slow query — a UDF where a built-in would do, a needless wide select, a forced shuffle join, a filter applied too late — which you’ll diagnose from its plan and fix one measured step at a time, until it’s fast and still produces the verified answer. Start with Lesson 1, where the report you already wrote turns out to be a SQL query in disguise.

Lessons in this module

1 Temp Views & SQL Register a temp view and write CityFlow's zone-hour report as a SQL string — then prove the DataFrame API and SQL are two front-ends to one Catalyst engine by showing df.explain() and spark.sql(...).explain() produce a physical plan that is byte-identical once you erase the cosmetic node ids. 2 Reading .explain() Read a real Spark physical plan node by node — FileScan to Exchange to final HashAggregate — and learn the four plan trees that .explain(mode=extended) prints, on CityFlow's 240,917-bucket zone-hour report. 3 Catalyst's Optimizations Diff Catalyst's plans on the real taxi month: column pruning shrinks ReadSchema from 19 columns to 3 and cuts scanned bytes 2.52x, a pushed PULocationID filter skips zero row groups on a stats-less file so all 2,964,624 rows are still read, and a partitioned parquet you write yourself finally makes the skip real — 145,240 rows instead of 2,964,624. 4 UDFs & Their Cost A Python UDF is a black box the optimizer can't see through. Measure exactly what that costs — a built-in expression and SQL both finish in about a tenth of a second while the same logic in a Python UDF takes seven times longer — read the opaque BatchEvalPython node in the plan, and watch a filter fail to push through it. 5 Guided Project: Optimize a Slow Query Take a real analytical query that runs in nearly two minutes and make it finish in under a second — not by guessing, but by reading its plan, naming each pathology from the node that reveals it, and fixing them one measured step at a time until the same verified answer comes back 157 times faster.
Achievement

Complete all 5 lessons to finish the Spark SQL & Execution Plans module.

Start module
Sponsor

Keep DATATWEETS free. Help fund practical data, AI, and engineering lessons for learners worldwide.

Buy Me a Coffee at ko-fi.com