The Skew Tell

The stage page's Summary Metrics table gives each metric's spread across the stage's tasks: Min, 25th percentile, Median, 75th percentile and Max. The skew tell is a single division, the Max of the Duration row over its Median. The median is the right baseline because one straggler drags a mean upward while barely moving the middle task.

MetricMin25thMedian75thMax
Duration48 s2.9 min3.5 min4.1 min2.0 h
GC Time1 s4 s5 s7 s38 min
Shuffle Read Size / Records160 MB / 1.6M182 MB / 1.8M190 MB / 1.9M199 MB / 2.0M6.6 GB / 66M
Spill (Disk)0 B0 B0 B0 B6.4 GB

For stage 4 of the ranking job, 2.0 hours over 3.5 minutes gives 34. Duration alone cannot say why the task was slow, so divide a second row the same way: 66 million shuffle read records over 1.9 million gives 35. The 2 ratios match. The slowest task took 34 times as long because it read 35 times the rows, nearly all of them for a single title_id. That is data skew.

Max over medianWhat it means
Under 2Ordinary variation between tasks
2 to 5Open the task table and read the slowest 10 tasks
Above 5Skew or a sick node: decide which before changing code

These bands are a working heuristic from practice; Spark itself flags none of them.

Data skew or a slow node

A high duration ratio has 2 causes that need opposite responses. The records ratio separates them, and the task table's Executor ID and Host columns confirm the call.

Data skew
  • Records ratio close to the duration ratio
  • The slow tasks read far more shuffle records
  • Several slow tasks sit on different executors
  • A rerun is slow at the same task index, wherever it lands
Slow node
  • Records ratio near 1
  • The slow tasks read an ordinary share of records
  • The slow tasks share one Executor ID and Host
  • A rerun whose tasks land elsewhere runs at normal speed

Here is the task table from a different job's slow stage, sorted by Duration, longest first. Its median task runs 11 s, so the duration ratio is 13, which looks like skew until you read the last column. The first 40 rows all carry executor 17; these are the top 3 and row 41:

RowTaskExecutor IDHostDurationShuffle Read Records
113111710.0.3.212.4 min1.2M
212901710.0.3.212.3 min1.1M
313471710.0.3.212.3 min1.1M
411318610.0.1.912 s1.1M

Records sit near 1.1 million for every task, and the slow ones share executor 17. The Aggregated Metrics by Executor table on the same page shows it from the other side: executor 17 spent 1.5 hours of task time on 40 tasks, while every other executor spent under 9 minutes on its 40. The data is even and one machine is slow, from a failing disk, a noisy neighbour or a throttled CPU. Salting would change nothing; the fix is to report the node and get it out of the pool.

A stage's Summary Metrics show a median Duration of 20 s and a max of 4.0 min. Shuffle Read Records show a median of 800K and a max of 840K. The 6 slowest tasks all ran on executor 9. What do you conclude?

The animation shows the data-skew side on a sort-merge join. Title 7 appears in 10 of the 15 plays, so whichever reduce task hash partitioning assigns to key 7 receives at least 11 rows, the 10 plays plus the title itself, while the other tasks split the remaining handful.

spark.conf.set("spark.sql.autoBroadcastJoinThreshold", -1)
plays.join(titles, "title_id").count()
plays
title_idprofile_id
7101
7102
3103
7104
7105
5106
7107
7108
9109
7110
3111
7112
7113
5114
9115
titles
title_idtitle
3Cooking Duel
5Deep Sea
7Big Hit
9Pilot Night

When the tell reads data skew, the levers are ones you already have: salt the hot key, isolate it and process it separately, or filter it out first if the hot key is NULL. For a sort-merge join, adaptive execution splits any partition larger than both 5 times the median and 256 MB on its own. A window like stage 4's gets no such help, which is why its 66 million rows still ran on a single core.

Try the tell on the practice data. The cell counts events per user, then counts how many users sit at each event total. Read 2 values: the events figure where the running total of users first passes half of all users, which is the median, and the events figure in the last row, which is the max. Their ratio is the skew tell for any shuffle keyed on user_id.

About this lesson

This section is part of the The Spark UI: Intermediate lesson on DataDriven, the home of data engineering. Lessons cover SQL, Python, Spark, data modeling and pipeline architecture, and each section pairs its explanation with worked examples and practice you run as you go.