Menu

Apache Spark interview question · Question 5 of 5

What is data skew and how can you mitigate it?

  • Hard
  • optimization / debugging
  • ~10 min
  • High relevance
  • 2 min read
  • Updated Oct 2026

Short answer

Data skew means some keys have far more rows than others, so after a shuffle a few partitions are huge and their tasks run much longer while the rest of the cluster waits. I confirm it in the Spark UI by comparing maximum and median task time and shuffle read size in the slow stage. Then I fix it in order: let AQE's skew-join handling split partitions, broadcast the small side if possible, filter or separately process hot or NULL keys, and as a last resort salt the key.

On this page
  1. Detailed explanation
  2. Fixes
  3. Salting a join
  4. Skew in aggregations
  5. Common mistakes

Detailed explanation

Symptoms: one stage where most tasks finish in seconds and a handful take minutes; large shuffle read on those tasks; spills or out-of-memory errors on a single executor.

Common causes: NULL or default keys (-1, "unknown"), a dominant customer or product, low-cardinality join keys.

Fixes

  1. AQE skew join (on by default) splits oversized partitions in sort-merge and shuffled hash joins and replicates the matching partition of the other side. It does not split a skewed groupBy or window partition.
  2. Broadcast the small side so the skewed large side is never shuffled.
  3. Handle hot keys separately: filter NULL keys that cannot match, or process the top key in its own branch.
  4. Salting: spread a hot key across N sub-keys.

Salting a join

from pyspark.sql import SparkSession, functions as F

spark = SparkSession.builder.master("local[2]").appName("example").getOrCreate()

N = 8
facts = spark.range(0, 50_000).withColumn("key", F.when(F.col("id") % 10 < 8, F.lit("hot")).otherwise(F.col("id").cast("string")))
dims = spark.createDataFrame([("hot", "big customer")], ["key", "label"])

salted_facts = facts.withColumn("salt", (F.rand(seed=1) * N).cast("int"))
salted_dims = dims.crossJoin(spark.range(N).withColumnRenamed("id", "salt"))

joined = salted_facts.join(salted_dims, ["key", "salt"], "left")
print(joined.filter(F.col("label").isNotNull()).count())
40000

The 40,000 “hot” rows are now spread across 8 salt values instead of one partition, and every one still finds its match because the dimension row was replicated for each salt.

Skew in aggregations

Aggregations are skewed less often than joins, because Spark already pre-aggregates on the map side: for sum, count, min or max, each input partition sends one partial row per key, however hot the key is. Skew bites when the aggregate cannot be combined early (collect_list, collect_set, exact countDistinct, percentiles) or when a window function partitions by a hot key. Then aggregate in two phases: group by (key, salt) first, then group the partial results by key.

partial = (facts.withColumn("salt", (F.rand(seed=2) * N).cast("int"))
                .groupBy("key", "salt").agg(F.collect_set("id").alias("ids")))
final = partial.groupBy("key").agg(F.size(F.flatten(F.collect_list("ids"))).alias("n_ids"))
print(final.filter(F.col("key") == "hot").first())
Row(key='hot', n_ids=40000)

The hot key’s 40,000 values are first collected in 8 smaller groups, then merged.

Common mistakes

  1. Salting without confirming skew in the UI.
  2. Forgetting to replicate the other side when salting a join, which drops matches.
  3. Assuming AQE handles aggregation skew.

By DataDank Editorial · Last reviewed Oct 2026 · Examples run on PySpark 4.2 in local mode; behaviour notes say where Spark 3.x differs

Progress is saved in this browser only. No account needed.

Search
Filter by type