Catalyst Optimizer: Beginner
DataFrames over RDDs
- You write the exact procedure
- Spark runs your steps as given
- Opaque functions Spark cannot inspect
- No optimization; your ordering is final
- You describe the result you want
- Spark chooses how to compute it
- Relational ops Spark understands
- Catalyst rewrites it for efficiency
The same intent, optimized for you
(products
.filter(F.col("in_stock") == 1)
.groupBy("category")
.agg(F.count(F.lit(1)).alias("n"))
.orderBy(F.col("n").desc(), F.col("category").asc()))Your Query Gets Rewritten
The RDD Escape Hatch
- Operations are transparent to Catalyst
- Filters push down, columns prune
- Tungsten generates fast fused code
- The optimizer works for you
- Your function is opaque to Catalyst
- The optimizer cannot see through it
- No pushdown, no pruning across it
- Steps run as written, unoptimized
DataFrame, Dataset, and RDD
| API | What Spark knows | What you get / give up |
|---|---|---|
| RDD | Nothing; opaque functions on opaque objects | Maximum control, zero optimization |
| DataFrame | Named columns and relational ops | Full Catalyst + Tungsten, untyped rows |
| Dataset | Columns AND your object types (JVM) | Optimization plus compile-time type safety |
Seeing It with explain()
| In the plan | What it tells you | Why it matters |
|---|---|---|
| PushedFilters at the scan | Your filter runs at the data source | Fewer rows are ever read; this is the win |
| A separate Filter operator | The filter did not push down | Investigate; you may be reading too much |
| Project with fewer columns | Column pruning happened | Only needed columns are carried |
| Exchange | A shuffle Catalyst planned | The wide operations, where cost concentrates |
Confirm the pushdown yourself
> From order_items, keep only rows with quantity above 1, then return total revenue (quantity times unit_price) per product_id, highest first. Catalyst will push the filter toward the scan so the aggregation sees fewer rows.
(order_items .(F.col("quantity") > 1) .groupBy("product_id") .agg(F.(F.col("quantity") * F.col("unit_price")).alias("revenue")) .orderBy(F.col("revenue").desc()))
- Stay in the DataFrame or SQL API so Catalyst can optimize your query.
- Write queries in the order that reads clearly; let the optimizer reorder for speed.
- Call explain before a big job to confirm filters pushed down and no surprise shuffle appeared.
- Prefer built-in functions and SQL expressions over opaque UDFs and RDD functions.
- Don't drop to an RDD for something the DataFrame API can express; you blind the optimizer.
- Don't assume a filter pushed down; read the plan and confirm PushedFilters at the scan.
- Don't reach for a UDF reflexively; it is a black box Catalyst cannot see through.
- Don't hand-tune operation order in DataFrames; Catalyst already does it better.
> You inherit a Spark job written mostly with RDD map and filter functions that runs slower than a colleague expects for its data size. You are asked to make it faster without changing what it computes.
You stopped telling Spark how, and started telling it what.
- Category
- Spark
- Difficulty
- beginner
- Duration
- 13 minutes
- Challenges
- 2 hands-on challenges
Topics covered: DataFrames over RDDs, Your Query Gets Rewritten, The RDD Escape Hatch, DataFrame, Dataset, and RDD, Seeing It with explain()
Lesson Sections
- DataFrames over RDDs (concepts: sparkDataFrameBasics)
Spark has two ways to express a computation, and they split along command versus request. The older way is the RDD, a Resilient Distributed Dataset, where you write the actual steps: map this function over the data, then filter with that one, then reduce in this particular order. You hand Spark a procedure, and Spark runs it more or less as you wrote it. An inefficient ordering stays inefficient, because all Spark sees is a sequence of opaque functions it must execute faithfully. The newer way i
- Your Query Gets Rewritten (concepts: paCatalystOptimizer)
Between the moment you describe a DataFrame and the moment it runs, Catalyst rewrites it. These are not minor cleanups but a series of transformations that can change your query substantially while guaranteeing the same result. Start with the two simplest and most impactful rewrites: constant folding and filter pushdown. Constant folding is the easy one. If your query contains an expression that can be computed without looking at the data, like a comparison against two plus three, Catalyst compu
- The RDD Escape Hatch (concepts: sparkRddApi)
Spark still lets you drop down to RDDs whenever you want, and occasionally you genuinely need to, for a transformation the DataFrame API cannot express. Understand what you give up when you do, because the cost stays invisible until you measure it: the moment you convert a DataFrame to an RDD and apply your own function, the optimizer goes blind. Catalyst can optimize DataFrames because it understands the relational operations. An RDD transformation is an arbitrary function, a black box that tak
- DataFrame, Dataset, and RDD (concepts: sparkRddApi)
Spark exposes three APIs for distributed data, and they sit on a spectrum from most optimized to most flexible. Knowing where each sits, and what it trades, lets you choose deliberately instead of by habit. The three are the RDD, the DataFrame, and the Dataset, and the axis that separates them is how much Spark understands about your data and operations. The RDD knows nothing: it is a distributed collection of arbitrary objects, and Spark runs whatever functions you give it without understanding
- Seeing It with explain() (concepts: paCatalystOptimizer)
You do not have to take the optimizer on faith; you can watch it work. The explain method prints the plan Spark intends to run, and comparing it to the query you wrote shows what Catalyst changed. The clearest thing to look for is a filter you wrote showing up in the plan pushed down to the scan, proof that the optimizer moved your work to where it costs least. You read a plan from the bottom up, because that is the order data flows: the leaves are the scans that read your tables, and each opera