← Field notesData

Apache Spark: DataFrames and the optimizations behind them

Why Spark replaced raw MapReduce, what DataFrames really are, and how Catalyst and Tungsten make them fast.

Orbyte · June 20, 2026 · 10 min read

Apache Spark: DataFrames and the optimizations behind them

MapReduce made Big Data possible, but it was slow and clumsy: every step wrote its results to disk before the next could start. Apache Spark, started at UC Berkeley's AMPLab in 2009 and donated to the Apache Software Foundation in 2013, kept the distributed, fault-tolerant idea but moved the working data into memory — and made it far easier to express a pipeline.

From RDDs to DataFrames

Spark's first abstraction was the RDD (Resilient Distributed Dataset): an immutable, partitioned collection you transform with map/filter/reduce. RDDs are powerful but low-level — Spark can't see inside your functions to optimize them. The DataFrame, introduced in Spark 1.3 (2015), is a distributed table with named, typed columns. Because Spark understands its structure, it can plan and rewrite the work before running a single line.

# A DataFrame pipeline reads like SQL — and Spark optimizes it as a whole.
df = spark.read.parquet("events")
result = (df
    .filter(df.country == "FR")
    .groupBy("product_id")
    .count()
    .orderBy("count", ascending=False))
result.show()  # nothing ran until this action

Lazy evaluation and the DAG

Spark is lazy: transformations only build a plan. Nothing executes until an action (like show or write) forces a result. Spark turns that plan into a DAG — a directed graph of stages — so it can pipeline operations, run independent branches in parallel, and recover lost partitions by recomputing just the affected lineage.

A bolt of lightning across a dark sky.
The name fits: Spark's edge is speed — keeping data in memory and optimizing the whole plan.

Catalyst and Tungsten

Two engines do the heavy lifting. Catalyst is Spark SQL's query optimizer: it rewrites your DataFrame plan with rules like predicate pushdown (filter as early as possible), column pruning (read only the columns you need) and join reordering. Tungsten then optimizes execution itself — managing memory off-heap, generating compact bytecode, and laying data out cache-friendly. You write a readable pipeline; these two turn it into an efficient one.

  • Predicate pushdown — apply filters before reading/joining.
  • Column pruning — read only the columns the query touches.
  • Partitioning & caching — avoid recomputation and shuffles.

Sources & further reading

Stay in orbit

New field notes, straight to your inbox.

Occasional engineering notes on what we build and how. No spam — unsubscribe anytime.

We store only your email — see our privacy policy.