The RDD Escape Hatch

Every DataFrame has an .rdd attribute. It returns the same data as an RDD of Row objects, and from there map accepts any Python function, as do filter and reduceByKey. Engineers reach for it when a step reads more naturally as a lambda, or out of habit carried over from older code.

orders = spark.read.parquet("/data/lake/orders")

totals = (
    orders.rdd
    .filter(lambda r: r.status == "Completed")
    .map(lambda r: (r.region, r.profit))
    .reduceByKey(lambda a, b: a + b)
    .toDF(["region", "total_profit"])
)

This produces the totals you saw in the DataFrames over RDDs section, at a higher price. The expression orders.rdd is a wall in the plan. Catalyst optimizes what lies before the wall, here the bare read, and then hands every row across it. Everything past the wall stays out of Catalyst's view until toDF builds a fresh DataFrame from the result.

The cost of the .rdd wall

A job reads a 120-column table, calls .rdd, and its lambdas read 2 fields. How many columns does Spark produce for each row it hands to the lambdas?

Where you place the wall decides how much you lose. DataFrame operations written before .rdd are still optimized. Filter and select first, and less crosses the wall:

completed = (
    orders
    .filter(F.col("status") == "Completed")
    .select("region", "profit")
)

totals = (
    completed.rdd
    .map(lambda r: (r.region, r.profit))
    .reduceByKey(lambda a, b: a + b)
    .toDF(["region", "total_profit"])
)

Now only Completed rows cross the wall, carrying 2 columns each, so far fewer Python objects get built. The better fix keeps the job on the near side of the wall entirely, since each of these lambdas has a DataFrame equivalent.

Translating lambdas back to columns

RDD habitDataFrame equivalent
`.map(lambda r: (r.region, r.profit))``.select("region", "profit")`
`.filter(lambda r: r.profit > 0)``.filter(F.col("profit") > 0)`
`.map(lambda r: r.region.upper())``.select(F.upper("region"))`
`.reduceByKey(lambda a, b: a + b)` on region pairs`.groupBy("region").agg(F.sum("profit"))`
`.sortBy(lambda r: r.profit, ascending=False)``.orderBy(F.col("profit").desc())`
`.map(lambda r: 1).sum()``.count()`

Jobs that need the RDD API

Some jobs have no column-expression form, and those are what .rdd exists for:

This cell does the job of an old RDD program that counted products and found the top price per category with map and reduceByKey. Run it, then compare: F.max skips products whose price is NULL, while Python's max given a None would raise a TypeError and fail the task.

About this lesson

This section is part of the Catalyst Optimizer: Beginner 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.