Here's a small Spark program: two DataFrames, one repartitioned into 5 partitions and the other into 6, joined and summed. Open the Spark UI and one of its stages has 200 tasks. Nothing in the code says 200.
Where that number comes from is the key to reading the Spark UI:
One action creates a job. Every shuffle starts a new stage. Every partition becomes a task.
This post walks through each of those three rules with the Spark UI open, and ends with where the 200 came from.
Every Action Creates a Job#
Each application is made up of one or more Spark jobs, and a job is created for count, collect, write, …). Jobs triggered from the same thread run one after another. They only run in parallel if you submit them from separate threads.
Since Spark evaluates lazily, transformations alone do nothing. If there are no actions, there are no jobs:

Call an action on the DataFrame, and a job appears:

Each job breaks down into a series of stages, and the number of stages depends on how many shuffles the job needs.
Every Shuffle Starts a New Stage#
A stage is a group of tasks that Spark can run together to perform the same operation across multiple machines. Spark tries to fit as much work as possible into one stage, but it has to start a new one whenever there's a shuffle.
A shuffle happens when
scala> val df1 = spark.range(2, 10000000, 2)
val df1: org.apache.spark.sql.Dataset[Long] = [id: bigint]
scala> val step1 = df1.repartition(5)
val step1: org.apache.spark.sql.Dataset[Long] = [id: bigint]
scala> step1.count()
val res0: Long = 4999999

One action (count), so one job, split into three stages:
- Stage 0 is the
rangethat creates the DataFrame. On this machine,rangeproduced8 partitionsby default. - Stage 1 is the
repartition. It shuffles the data into5 partitions. - Stage 2 is the
countitself.
Now the program from the start of this post:
val df1 = spark.range(2, 10000000, 2)
val df2 = spark.range(2, 10000000, 4)
val step1 = df1.repartition(5)
val step12 = df2.repartition(6)
val step2 = step1.selectExpr("id * 5 as id")
val step3 = step2.join(step12, ["id"])
val step4 = step3.selectExpr("sum(id)")


Stages 3 and 4 work on each DataFrame separately, and each ends at the join, which is another shuffle. The stage after the join is the one with 200 tasks.
Every Partition Becomes a Task#
A task is a
That's where the 200 comes from. When a DataFrame shuffle happens, the number of output partitions isn't taken from your input; it's taken from spark.sql.shuffle.partitions, which defaults to 200. The join shuffled both sides into 200 partitions, so the next stage ran 200 tasks, whether the data needed them or not.
You can change it:
spark.conf.set("spark.sql.shuffle.partitions", 50)
Here is the whole job, one row per stage and one square per task. Change the setting and run it again:
Create df1
range() made 8 partitions on this machine
8 tasks
Create df2
range() again, 8 partitions
8 tasks
After repartition(5)
the shuffle wrote 5 partitions
5 tasks
After repartition(6)
the shuffle wrote 6 partitions
6 tasks
After the join
the join shuffle wrote spark.sql.shuffle.partitions = 200
200 tasks
Final sum
everything is combined into 1 partition
1 task
One action, so one job. Each row below is a stage, and each square is a task.
The Whole Picture in One Table#
| Topic | Key Points |
|---|---|
| App Decomposition | - 1 Spark Application can have 1 or more Jobs. - Each Job is broken into 1 or more Stages. |
| Stages & Tasks | - Each Stage is subdivided into Tasks. - Shuffle boundaries (data rearrangements) define when Spark moves from one Stage to the next. |
| Tasks & Executors | - One Task runs on one Executor (it can’t move mid-task). - An Executor can run multiple Tasks (depending on its available cores). |
| Partitions & Tasks | - Processing one partition = one Task. - The number of Tasks in a Stage often equals the number of partitions for that data. |
| Partitions & Executors | - A partition stays on one Executor while it’s being processed. - Each Executor can hold 0 or more partitions in memory or on disk. |
| Executors & Nodes | - 1 Executor corresponds to 1 JVM running on 1 physical/virtual Node. - Each Node can host 0 or more Executors. |
Or in one line: Application → Jobs → Stages → Tasks, where each task processes one partition on one executor.
References#
The next time the Spark UI shows a number you didn't write, look for the shuffle before it. That's where the 200 came from.
More in the Apache Spark series.