Transformations, actions and lazy evaluation

PySpark · Essentials · 10 min

Lazy evaluation explains when work happens, why errors show up late, and why repeated actions recompute everything.

Spark operations fall into two groups.

Transformations (select, filter, withColumn, join, groupBy().agg, orderBy) are lazy. They return a new DataFrame describing a computation but run nothing. Spark only builds up a logical plan.

Actions (count, show, collect, take, first, write...save, toPandas) need a result, so they make Spark optimize the plan and run it as a job, split into stages and tasks on the executors.

Consequences:

  • A typo in a column name is caught when the plan is analyzed, which happens eagerly on most transformations. But a runtime problem, such as a bad cast under ANSI mode, appears only when an action runs, possibly far from the line that caused it.
  • Every action recomputes the plan from the source unless you cache the DataFrame. Calling count() and then show() reads the input twice.
  • Spark can optimize across your whole chain, for example pushing a filter written after a join down into the file scan.

Transformations are also classed as narrow (each output partition depends on one input partition: filter, select) or wide (data must move between partitions, a shuffle: groupBy, join, orderBy, distinct). Wide transformations create stage boundaries and are where most time goes.

Example

from pyspark.sql import SparkSession, functions as F
spark = SparkSession.builder.master("local[2]").getOrCreate()

df = spark.range(5_000_000)
heavy = (df.withColumn("sq", F.col("id") * F.col("id"))
           .filter(F.col("id") % 7 == 0)
           .groupBy((F.col("id") % 3).alias("k")).count())
print(type(heavy).__name__)       # DataFrame: only a plan so far, no job has run

rows = heavy.orderBy("k").collect()            # action: the job runs now
print([tuple(r) for r in rows])
# [(0, 238096), (1, 238095), (2, 238095)]
print(heavy.count())                           # 3  (recomputed from scratch)

Remember

  • Transformations are lazy and build a plan; actions run jobs
  • Each action recomputes the plan unless cached
  • Narrow transformations stay in a stage; wide ones shuffle

Watch out for

  • Calling count() 'just to check' between steps, each time recomputing the whole pipeline.

Check yourself

  1. Which of these triggers a Spark job?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: spark.apache.org/docs/latest/api/python/user_guide/dataframes.html