Catalyst Optimizer: Intermediate
Analysis
Logical Optimization
| Rule | What it does | The win |
|---|---|---|
| Predicate pushdown | Move filters toward the scan | Fewer rows enter every later step |
| Projection pushdown | Drop unused columns early | Less data read and carried |
| Constant folding | Evaluate constants once at plan time | No per-row recomputation |
| Boolean simplification | Simplify redundant conditions | Cheaper, clearer filters |
Physical Planning
- Says: join these two on this key
- Says: aggregate by this column
- No execution strategy chosen yet
- Produced by rule-based optimization
- Says: broadcast hash join, or sort-merge
- Says: hash aggregate, in this many partitions
- Concrete, runnable strategies
- Chosen using size estimates (cost-based)
The join whose strategy the optimizer picks
(order_items
.join(products, "product_id")
.groupBy("category")
.agg(F.sum("quantity").alias("units"))
.orderBy(F.col("units").desc()))Cost-Based Optimization
| With statistics (CBO on) | Without statistics | The consequence |
|---|---|---|
| Accurate size estimates per step | Crude guesses from file bytes | Right vs wrong join strategy |
| Selectivity from value distribution | Rule-of-thumb percentages | Right vs wrong broadcast decision |
| Cost-based join ordering | Join in the order written | Small vs large intermediate results |
A selective filter the optimizer prices
> From products, keep only highly rated items (rating at least 4.5), then return the average price per category, highest average first. The optimizer estimates how many rows the rating filter keeps to plan the aggregation.
(products .(F.col("rating") >= 4.5) .groupBy("category") .agg(F.("price").alias("avg_price")) .orderBy(F.col("avg_price").desc(), F.col("category").asc()))
Reading a Physical Plan
| In the physical plan | What it means | What to do with it |
|---|---|---|
| Exchange | A shuffle (a wide operation) | Count them; each is a stage boundary and a cost |
| BroadcastExchange | A small side broadcast for a join | Confirms a broadcast hash join was chosen |
| *(n) marker | Whole-stage codegen fused n operators | Those operators run as one compiled loop |
| PushedFilters | A filter pushed to the scan | Confirms your filter reads less data |
| HashAggregate / SortAggregate | The aggregation strategy chosen | Hash is usual; sort implies a sorted input |
- Read a query as four phases: analysis, logical optimization, physical planning, codegen.
- Trust rule-based rewrites (pushdown, pruning) as always-correct; watch cost-based join choices.
- Run ANALYZE TABLE so CBO has fresh statistics to choose join order and strategy.
- Read the physical plan bottom up: count Exchanges, check join operators, confirm PushedFilters.
- Don't assume the join strategy is optimal; it came from estimates that can be wrong.
- Don't let table statistics go stale; the optimizer trusts them and stale stats mislead it.
- Don't ignore where the *(n) codegen markers stop; that is where optimization broke down.
- Don't confuse the logical plan (what) with the physical plan (how); the costs live in the how.
> A query that joins three tables has gotten slow since one of the tables grew, and the physical plan shows a SortMergeJoin with shuffles where you expected a broadcast. The tables were last analyzed months ago.
A query flows through four stages, and the trouble is always in one of them.
- Category
- Spark
- Difficulty
- intermediate
- Duration
- 14 minutes
- Challenges
- 2 hands-on challenges
Topics covered: Analysis, Logical Optimization, Physical Planning, Cost-Based Optimization, Reading a Physical Plan
Lesson Sections
- Analysis (concepts: paCatalystOptimizer)
Catalyst's first move is unglamorous and unavoidable: it resolves your query against the catalog. When you wrote select category from products, the optimizer does not yet know that products is a real table, that category is a real column, or what type that column is. Analysis binds every name you used to a concrete thing in the catalog, the metadata store that knows which tables and columns exist and their types. This is where the errors you actually see come from. A misspelled column name, a ta
- Logical Optimization (concepts: paCatalystOptimizer)
With a resolved plan in hand, Catalyst enters the phase most people mean when they say optimization: logical optimization, a battery of rule-based rewrites that transform the plan into an equivalent but cheaper one. These are the optimizations from the beginner tier, now placed in their proper home. Each rule is a small, provably-correct transformation, and Catalyst applies them repeatedly until the plan stops changing. The headline rules are the ones that move and shrink work. Predicate pushdow
- Physical Planning (concepts: paCatalystOptimizer)
Logical optimization produces an optimized logical plan that says what to compute but not how. Physical planning is where Catalyst decides the how: it generates one or more physical plans, concrete strategies for actually running each operation, and chooses among them. This is the phase where a groupBy becomes a specific aggregation strategy and, most importantly, where a join becomes a specific join algorithm. Focus on the join strategy choice, where physical planning has its largest effect on
- Cost-Based Optimization (concepts: paCatalystOptimizer)
The choices physical planning makes are only as good as the size estimates behind them, and cost-based optimization, CBO, is the part of Catalyst that tries to make those estimates accurate using real statistics about your tables. Without statistics, Catalyst falls back to crude heuristics, guessing sizes from file bytes and rule-of-thumb selectivity. With statistics, it can estimate the size of each intermediate result and choose join orders and strategies that minimise the total work. Statisti
- Reading a Physical Plan (concepts: paSparkUiDiagnosis)
All four phases end in a physical plan, and being able to read it is the practical payoff of understanding the phases. The plan is a tree of operators, printed by explain, and you read it from the bottom up because that is the order data flows: leaves are scans, and each operator consumes the output of the one below. A handful of operators and markers carry most of the meaning, and once you know them the plan reads as a diagnosis. The first thing to do with any plan is count the Exchange nodes,