DSC3103 Β· Module 5 of 9

Distributed Batch Processing with PySpark

Partitions, lazy evaluation, shuffles & caching (Lab 6 / Lab 8)

~56 min to study properly

When datasets outgrow a single machine's RAM, Apache Spark distributes data across a cluster as partitioned DataFrames. A Driver coordinates the execution plan while Executors process partitions in parallel.

Checkpoint Β· the examiner asks

Explain the difference between narrow and wide transformations in PySpark. Why did DuckDB beat PySpark on our 10,000-row market dataset, and when does `.cache()` actually pay off?

In one minute

Spark splits DataFrames into partitions processed in parallel by executors; one partition is one task, and stages run in waves of ceil(partitions / cores). Transformations are lazy until an action runs. Narrow transformations stay inside a partition; wide ones (groupBy, join) shuffle data across the network. On small data JVM and shuffle overhead dominate, so DuckDB can win and .cache() can even slow things down.

Traps examiners look for
  • Expecting a transformation to run before an action is called.
  • Caching a DataFrame that is only used once.
  • Choosing Spark for data that fits comfortably on one machine.
Read

Study notes

01Driver, executors and partitions

Spark spreads a DataFrame across a cluster as partitions β€” chunks of rows. A driver program plans the work; executors run it. Each executor has some cores, and each core processes one partition at a time as a task.

So the basic arithmetic is:

  • 1 partition β†’ 1 task.
  • Tasks run in waves of (total executor cores): waves = ⌈partitions Γ· coresβŒ‰.
  • Stage time β‰ˆ waves Γ— time per task.

20 partitions on 8 cores need 3 waves β€” and the last wave uses only 4 cores while 4 sit idle. Too few partitions leaves cores idle; far too many creates scheduling overhead. A common rule of thumb is 2–4 partitions per core.

02Lazy evaluation, actions and Catalyst

Transformations (filter, select, withColumn, groupBy, join) don't execute β€” they add steps to a logical plan. Only an action (count, collect, show, write) triggers execution.

Laziness lets Spark's Catalyst optimiser rewrite your whole plan before running it: push filters down to the data source, drop unused columns, reorder joins, then the Tungsten engine generates efficient code. df.explain() shows the physical plan β€” look for Exchange (a shuffle) and PushedFilters.

The flip side: every action re-runs the plan from the source unless you cache. Two count()s on the same DataFrame read the data twice.

03Narrow vs wide β€” the shuffle

  • Narrow transformations (filter, select, withColumn, map) work inside each partition. No data moves between machines.
  • Wide transformations (groupBy, join, orderBy, distinct) need rows with the same key to end up together. Spark shuffles: hashes each row's key, writes partition files to disk, and sends them across the network. That's the Exchange in the plan and usually the most expensive part of a job.

Spark reduces shuffle cost with partial aggregation: each partition first computes its own partial sums per key, so at most partitions Γ— distinct keys rows are shuffled instead of every row. That's why groupBy("commodity").avg("price") on 10 million rows shuffles only a few thousand.

04When Spark is the wrong tool

Spark's fixed costs β€” starting a JVM, planning, serialising tasks, scheduling, shuffles β€” are seconds, not milliseconds. On small data they dominate. In Lab 8 on 10,000 rows, DuckDB took 1.39 s and Spark 11.21 s.

.cache() also isn't free: the first action has to materialise the cached data in memory. It pays off only when the cached DataFrame is reused by enough later actions. In our benchmark, caching a DataFrame used by two actions made the script slower (8.85 s vs 3.04 s).

Rule of thumb: if the data fits comfortably on one machine, use a single-node engine (DuckDB, Polars, pandas). Reach for Spark when data or compute genuinely exceeds one machine.

Watch it done

Worked examples

Worked example 1

Waves and idle cores

A stage has 26 partitions, each task taking 5 s, on a cluster with 3 executors Γ— 4 cores. How long does the stage take, and how many core-slots are idle in the last wave?

    Worked example 2

    Cache break-even

    Each action on an uncached DataFrame takes 3 s. Caching costs an extra 4.5 s on the first action; later actions take 0.5 s. From how many actions does caching win?

      Vocabulary

      Key terms

      Single-node first: most 'Big Data' fits in laptop RAM

      A 2023 study of production cloud analytics jobs found that over 90% of queries scanned less than 10 GB of data. Modern engineers use DuckDB or Polars for <50 GB and reserve Spark for true multi-hundred-gigabyte distributed workloads.

      Practise

      Quick check

      0 of 8 correct so far. Wrong answers come with an explanation β€” and the buddy can unpack them.

      Q1In `df.filter(...).withColumn(...).groupBy('market').agg(avg('price')).collect()`, which call is the Action that triggers execution?

      Q2You have 1,000,000 rows across 4 partitions and 5 distinct markets. With partial aggregation (`HashAggregate` before `Exchange`), at most how many intermediate rows are sent across the shuffle network?

      Q3Which operation triggers execution in Spark?

      Q4Which transformation causes a shuffle?

      Q5What does 'Exchange' in df.explain() indicate?

      Q640 partitions, 16 cores, 2 s per task. Stage time?

      Q7Why can DuckDB beat Spark on 10,000 rows?

      Q8What does partial aggregation reduce?

      Exam-style

      Written practice

      Short answer5 marks

      Explain lazy evaluation in Spark and why it enables optimisation.

      Short answer6 marks

      Contrast narrow and wide transformations and explain why shuffles are expensive.

      Long answer12 marks

      Your team runs a PySpark job on 10,000 rows of market data that takes 11 s, uses .cache() on a DataFrame used by two actions, and shuffles twice. A teammate's DuckDB script does the same in 1.4 s. Explain every reason for the difference, when Spark would be the right choice, and how you would tune the Spark job if the data grew to 500 million rows.

      Watch

      Videos

      Fresh picks are chosen by AI every day.
      Hands-on

      PySpark Partition Waves, Shuffle & Cache Break-Even

      4 guided tasks: predict, experiment, explain β€” then challenge yourself. Plus 2 coding exercises you run right in the browser.

      Start the lab