Execution Model: Beginner
Driver, Executors, and Tasks
Why this matters the moment a job is slow
- One process plans and executes the query
- All the data lives in one place
- You scale by buying a bigger machine
- The planner and the worker are the same thing
- The driver plans; the executors execute
- Data is spread across many machines
- You scale by adding more machines
- The planner (driver) never touches a row
Which actor in a Spark cluster plans the job and hands out work, but never processes data itself?
Partitions and Parallelism
Too many, too few, or just right
| Partition count | What happens | The symptom you'd see |
|---|---|---|
| Far too few (e.g. 4) | Most of the cluster idles; a few huge tasks | Slow job, low CPU usage, occasional out-of-memory errors |
| Far too many (e.g. 500,000 tiny ones) | Per-task scheduling overhead dominates the real work | Slow job, a busy driver, task times in milliseconds |
| About right (~128 MB each) | Every core stays busy; tasks finish in seconds | Steady, high CPU usage across the whole cluster |
The 128 megabyte target is a default, not a law. It is governed by spark.sql.files.maxPartitionBytes, and it exists because that size balances the cost of starting a task against the cost of holding a partition in memory. You will learn to tune it. For now, hold the shape: a partition is a chunk of rows, one task chews through exactly one chunk, and the number of chunks sets how parallel the whole job can ever be.
(order_items
.join(products, "product_id")
.groupBy("category")
.agg(F.sum(F.col("quantity") * F.col("unit_price")).alias("revenue"))
.orderBy(F.col("revenue").desc()))> Complete the aggregation so it totals price per category. You supply the wide operation that regroups rows by key, and the sum that runs within each group.
(products .("category") .agg(F.("price").alias("revenue")))
Transformations vs Actions
- filter, select, groupBy, join, withColumn
- Return instantly and run nothing
- Just append a step to the plan
- You can chain dozens for zero cost
- count, collect, write, show, take
- Block until real work finishes
- Force the whole accumulated plan to execute
- This is the line where the bill comes due
Why your stack trace lies to you
(products
.filter(F.col("in_stock") == 1)
.groupBy("category")
.agg(F.count(F.lit(1)).alias("in_stock_count"))
.orderBy(F.col("in_stock_count").desc()))> Keep only in-stock products, then count them per category. You supply the lazy filter and the column the count groups by.
(products .(F.col("in_stock") == 1) .groupBy("") .agg(F.count(F.lit(1)).alias("n")))
Cores and Slots
When more hardware does nothing
This is exactly why "just add more executors" is sometimes the right call and sometimes useless. More slots help only when there are enough partitions to fill them. Before you scale the cluster, do the division: if partitions are already fewer than your current slots, more hardware cannot help, and the lever you actually want is repartitioning the data.
A stage has 200 partitions and the cluster has 50 slots (executors x cores). How many waves does the stage run in?
A Job's Life, End to End
The one-sentence version to say cold
(order_items
.join(products, "product_id")
.filter(F.col("in_stock") == 1)
.groupBy("category")
.agg(F.sum("quantity").alias("units_sold"))
.orderBy(F.col("units_sold").desc()))- Reason about a job in execution-path order: action, then plan, then partitions, then tasks on slots, then results.
- Match partition count to your slots so every executor core has a task to run instead of sitting idle.
- Reach for an action (count, collect, write) only when you actually need a result, since each one forces the whole plan to run.
- Pull only small results back to the driver with collect; write large results out from the executors instead.
- Don't assume more machines means a faster job; extra slots do nothing when there are not enough partitions to fill them.
- Don't expect a transformation to do any work on its own; nothing runs until an action triggers it.
- Don't collect a large dataset to the driver; it is a single machine and will run out of memory.
- Don't picture the driver processing rows; it plans and directs, while the executors are the only actors that touch data.
> You are a data engineer at an online marketplace asked to build a daily report of units sold per product category. The catalog and the order history are far too large for one machine, so the job runs on a Spark cluster: it reads the data, joins orders to products, filters to in-stock items, and aggregates by category.
Your query is a promise. Something has to keep it.
- Category
- Spark
- Difficulty
- beginner
- Duration
- 12 minutes
- Challenges
- 7 hands-on challenges
Topics covered: Driver, Executors, and Tasks, Partitions and Parallelism, Transformations vs Actions, Cores and Slots, A Job's Life, End to End
Lesson Sections
- Driver, Executors, and Tasks (concepts: paSparkExecutionModel)
Your database was one engine: it read your statement, planned the work, ran it, and handed back rows, all inside one process on one machine. Spark takes that single role and splits it across three separate actors. Get these three straight and most of Spark stops being mysterious, because every later idea in this lesson is really a statement about one of them. The driver is the process that runs your program: it holds your code, turns it into a plan, and decides what work needs doing. There is ex
- Partitions and Parallelism (concepts: paSparkExecutionModel)
Your data does not arrive at an executor as one big table. The very first thing Spark does with a dataset is cut it into chunks called partitions. A partition is a contiguous slice of the rows, typically targeted around 128 megabytes, that lives in memory on one executor. A billion-row table might become 8,000 partitions scattered across the cluster. This split is the single most important idea in all of Spark, because it is the unit of parallelism: one task processes exactly one partition, and
- Transformations vs Actions (concepts: paSparkExecutionModel)
Here is the thing that catches everyone coming from SQL. When you write df.filter(...).groupBy(...).agg(...), nothing runs. Spark does not read a single row. You have only described work. The description is called a transformation, and transformations are lazy: each one adds a step to a plan and returns immediately, having done no computation at all. You can chain 20 of them and the cluster stays idle the entire time. The data actually moves only when you call an action. This is not a quirk to w
- Cores and Slots (concepts: paSparkExecutionModel)
An executor is not a single worker. It has a number of cores, and each core can run one task at a time. So an executor with 5 cores is processing 5 partitions simultaneously. The cleanest way to picture it is as slots: a slot is a place where a task can be running right now. Your total parallelism -- the number of tasks that can be in flight across the whole cluster at any instant -- is simply the sum of all the slots on all the executors. This is the number that, together with the partition cou
- A Job's Life, End to End (concepts: paSparkExecutionModel)
Now we narrate one full run, using only the pieces you have built: driver, executors, cluster manager, partitions, tasks, slots, transformations, and actions. This is the answer to the single most common Spark interview opener -- "walk me through how Spark runs a job" -- and the trick to answering it well is to follow the path the work actually travels, rather than reciting a list of vocabulary. Each step hands off to the next, and naming the hand-offs in order is what separates a confident answ