Make It Faster
What you will be able to do
- ▸A job that used to take 20 minutes now takes 2 hours. Walk me through what you do.
- ▸This transformation is slow. How would you speed it up?
- ▸Here is a stage list from the Spark UI. What stands out to you?
- ▸A teammate says the fix is more executors. Do you agree?
What a complete answer contains
- LocateWhich stage owns the wall clockOpen the Jobs tab, then the Stages tab sorted by duration. One stage usually holds most of the time.
- MeasureWhat the tasks inside it look likeRead the task summary: median and max duration, shuffle read per task, spill, GC time.
- DiagnoseWhich cause matches that shapeA lopsided max says a hot key. Even slow tasks with spill say memory pressure or too few partitions.
- FixOne change aimed at that causeSplit the hot key out, raise the partition count, filter earlier, broadcast the small side.
- VerifyThe same stage, re-run, comparedThe stage's duration and its max task before and after, on the same input.
- Opens with a fix: 'I would add more executors.'
- Names 1 cause and stops
- Cannot say which stage it would check
- Has no way to show the fix worked
- Opens with where the time is: 'I would find the slowest stage.'
- Names several causes and the symptom that separates them
- Picks the fix the symptom points to
- Ends with the measurement that proves it
| Opening sentence | What the interviewer hears |
|---|---|
| I'd add more memory. | A fix chosen before a diagnosis. |
| It's probably skew. | A likely cause, with no plan to confirm it. |
| I'd open the Spark UI and find the stage that holds most of the time. | A route to the cause, which they can follow with you. |
Why the order matters
orders.groupBy("region").sum("amount").collect()
The hunt, narrated
Here is the Stages tab for the job from the intro, as you would describe it from memory. The job scans a day of viewing events, scans the title catalog, joins them on title_id, aggregates hours watched per title, and writes the result. None of this is visible in the interview room, which is why the narration has to carry every number.
| Stage | What it does | Tasks | Duration | Shuffle write | Shuffle read | Spill (disk) |
|---|---|---|---|---|---|---|
| 0 | Scan viewing_events, prepare join key | 400 | 4 min | 38 GB | 0 | 0 |
| 1 | Scan title_catalog | 8 | 20 s | 1.2 GB | 0 | 0 |
| 2 | Join on title_id, aggregate | 200 | 2 h 51 min | 0.9 GB | 39.2 GB | 24 GB |
| 3 | Write output | 200 | 3 min | 0 | 0.9 GB | 0 |
| Metric | Min | 25th | Median | 75th | Max |
|---|---|---|---|---|---|
| Duration | 22 s | 34 s | 41 s | 56 s | 2 h 48 min |
| Shuffle read | 60 MB | 110 MB | 135 MB | 160 MB | 12.4 GB |
| Spill (disk) | 0 | 0 | 0 | 0 | 24 GB |
| GC time | 0.4 s | 0.9 s | 1.2 s | 1.6 s | 4 min |
If the stage list is long
- I would open the Jobs tab and find the job holding most of the time.
- In its stages I would sort by duration. Stage 2, the join, has 2 h 51 min of it.
- I would open stage 2 and read the task summary: median 41 s, max 2 h 48 min.
- One task read 12.4 GB of the 39 GB shuffle, and the median task read 135 MB.
- That is a hot value in title_id, so the next check is which value it is.
- After the fix I would re-run on the same input and expect the max task close to the median.
When the interviewer says there is no UI, narrate what each screen would show anyway. The missing screen is the test, and the narration is the answer.
The usual suspects, cheapest check first
| Suspect | What the stage shows | First fix |
|---|---|---|
| Input grew | Scan stage input bytes up 3x on last week; all tasks evenly slower | Confirm the growth with the data owner; add a date filter or scale out rather than tune |
| Hot key | One task's duration and shuffle read far above the median; the rest finish fast | Find the hot value; split it out, filter it, or salt it |
| Too few shuffle partitions | Every task reads over 500 MB; spill on most of them | Raise `spark.sql.shuffle.partitions` so each task reads 100 to 200 MB |
| Too many tiny tasks | Thousands of tasks of 1 to 2 seconds; scheduling overhead dominates | Coalesce, or read fewer and larger files |
| Shuffle volume | Shuffle write in tens of GB for a result of a few MB | Filter and project before the join; broadcast a small side |
| Memory pressure | Spill on most tasks with even durations; GC time over 10% of task time | More partitions first, then fewer cores per executor, then more memory |
| Slow per-row code | Even slow tasks, little shuffle, no spill; a user function in the plan | Replace the function with built-in column operations |
Why saying them fast beats one good guess
- Name 3 to 5 causes, each with the UI symptom that confirms it
- Check input growth before any tuning
- Order the list by how cheap each check is
- Say which number on the stage page separates the top 2 causes
- Stop at 1 cause without a way to confirm it
- List causes without the symptom that distinguishes them
- Name causes the stage metrics have already ruled out
- Spend the first minute on cluster size
What memory fixes, and what it leaves alone
| Symptom | Does more memory help? | Why |
|---|---|---|
| 1 task far slower than the median | No | The hot task still reads its 12.4 GB alone; a bigger heap shortens its spill, and its row count stays the same |
| Most tasks spill, durations even | Yes | Each task's buffer now fits, so the spill disappears and the disk write with it |
| Shuffle write far above output size | No | The bytes still cross the network; memory does not reduce them |
| Thousands of 1-second tasks | No | Scheduling overhead has nothing to do with heap size |
| Input tripled since last week | No | Three times the rows need 3 times the work; memory only moves where the work happens |
| GC time over 10% of task time | Sometimes | A larger heap can lengthen each pause; more partitions or fewer cores per executor often do more |
- ▸Task durations are even: the max within 2x of the median
- ▸Spill shows on most tasks, rather than on 1
- ▸Shuffle read per task is modest, a few hundred MB at most
- ▸GC time is a visible share of task time on every task
- ▸No single value of the key dominates the input
The honest version of the memory answer
- Signals a reflex rather than a reading of the stage
- Costs money on every run
- Leaves a hot key, shuffle volume and tiny tasks untouched
- Gives the interviewer nothing to probe
- Signals that the symptom pointed there
- Comes after the free partition fix
- Names the task-level number it will change: spill
- Ends with the re-run that shows spill at 0
spark.conf.set("spark.sql.autoBroadcastJoinThreshold", -1)events.join(titles, "title_id", "left").count()
Seven of the 12 event rows carry no title_id. A left join keeps them, every one hashes to the same partition, and 1 task receives at least 7 of the 12 rows. Scale that ratio to a day of viewing events and the task that owns the empty key reads 12.4 GB while the median reads 135 MB. Without adaptive skew handling, the stage's duration is that task's duration however many executors the cluster has, because no other task can take a share of its partition. Adaptive execution, when enabled, can split a skewed join partition into several tasks, so 1 task of 2 h 48 min in the UI means it was off or did not apply. Splitting the empty rows out still matters: it removes their join work rather than spreading it.
Find the hot value
| title_id | event_rows | share of day |
|---|---|---|
| NULL | 61,400,000 | 31% |
| 80100172 | 2,900,000 | 1.5% |
| 81040344 | 2,100,000 | 1.1% |
| 80192098 | 1,800,000 | 0.9% |
| 70300500 | 1,500,000 | 0.8% |
| Stage 2 | Before | After |
|---|---|---|
| Duration | 2 h 51 min | 6 min |
| Max task duration | 2 h 48 min | 58 s |
| Median task duration | 41 s | 39 s |
| Max task shuffle read | 12.4 GB | 190 MB |
| Spill (disk) | 24 GB | 0 |
| Whole job | 2 h 58 min | 13 min |
- ▸
NULL, an empty string, 0 or -1 at the top is a placeholder for a missing value: filter it out of the join and pad its columns. - ▸A test account or a bot at the top is junk: drop its rows before the join, or upstream at the source.
- ▸A real title at the top is demand: its rows need the catalog, so salt that key or broadcast a small catalog.
- ▸Top keys at similar shares mean no hot key: return to the task summary and the suspect list.
> A streaming company's nightly job computes hours watched per title by joining a day of viewing events to the title catalog. After a client release starts logging app-open sessions with no title, 31% of the day's rows carry an empty key, the join stage balloons from minutes to nearly 3 hours, and the on-call engineer is asked in the morning stand-up what to do.
Find the stage, read its tasks, name the cause, pick the fix, prove it: the method a slow-job question grades.
- Category
- Spark
- Difficulty
- beginner
- Duration
- 14 minutes
- Challenges
- 3 hands-on challenges
Topics covered: A Question About Method, Narrate the Hunt for the Slow Stage, Name the Usual Suspects, "Just Add More Memory", One Stage Dominates
Lesson Sections
- A Question About Method (concepts: Spark UI Diagnosis)
The question arrives as a story. A nightly job that finished in 25 minutes last month now runs for nearly 3 hours, the code has not changed, and the pipeline owner wants it back under 30 minutes. The interviewer then stops talking. What they grade from that point is the order of what you say: whether you locate the slow part before you explain it, whether you explain it before you change it, and whether you say how you would know the change worked. A fix offered in the first sentence scores as a
- Narrate the Hunt for the Slow Stage (concepts: Spark UI Diagnosis)
The Spark UI has 1 screen per level of the hierarchy. The Jobs tab lists every action the program ran, with its duration and the stages it needed. The Stages tab lists every stage across all jobs, with its task count, its duration, its input size, and its shuffle read and shuffle write in bytes. Opening a stage shows the summary metrics for its tasks: min, 25th percentile, median, 75th percentile and max, for task duration, GC time, shuffle read size, and spill to memory and disk. Those 5 column
- Name the Usual Suspects (concepts: Spark UI Diagnosis)
The moment after you find the stage, the interviewer asks what could make it slow. Two answers lose. Silence loses because it shows no map of the causes. A single cause loses because, if it is wrong, the conversation has nowhere to go. A short list of named causes, each paired with the symptom that would confirm it, wins because it hands the interviewer a menu: they pick one, you go deeper, and if the first is wrong the second is already on the table. The list is short. Seven entries cover nearl
- "Just Add More Memory" (concepts: Spark UI Diagnosis)
Add more memory is the most common first answer to a slow job, and the interviewer hears it as a reflex. Memory in an executor does 1 thing for a running task: it holds the task's working set, the hash table or sorted buffer that a join or an aggregate builds, so the task avoids writing that buffer to disk. Spark reserves 300 MB of the heap, gives a fraction of the rest (0.6 by default) to execution and storage together, and divides that between the tasks running on the executor. When a task's b
- One Stage Dominates (concepts: Spark UI Diagnosis)
The commonest shape in this question is the one from the narration: 1 stage holds nearly all the job, and inside it 1 task holds nearly all the stage. The 199 ordinary tasks finish within the first few minutes and their executors sit idle for nearly 3 hours waiting for 1 task, because the stage cannot end until every task ends and the next stage cannot start until the stage ends. The cluster is 99% idle and the bill is 100% running. The shape has 1 cause in a join or an aggregate: a key value th