What Is Apache Spark? Distributed Data Processing in One Post

· tech

#spark#data-engineering#pyspark

📑 Contents

What Spark is

In one sentence: Apache Spark is an engine that takes data too big for one machine to hold or finish crunching, cuts it into many pieces, and computes them in parallel across a whole cluster. It came out of UC Berkeley in 2009 and became an Apache project in 2014; its headline feature is computing with the data held in memory, which makes it far faster than the older Hadoop MapReduce for work that reads the same data over and over (report aggregation, machine-learning iterations).

You write Python / SQL / Scala that looks like you’re manipulating one big table, and behind the scenes Spark breaks it into tasks, spreads them over dozens of machines, and gathers the results back.

How is this different from pandas or a single-machine program?

Dimensionpandas (single machine)Spark (distributed)
Data sizebounded by one machine’s memoryscales out across a cluster
Executionruns immediatelylazy: builds a plan, runs on an action
Parallelismone machine (several cores at best)many machines × many cores, parallel by partition
Failure recoveryyours to handlebuilt in (a failed task is recomputed)
Fitsa few MB to a few GBtens of GB to TB/PB

The point isn’t that Spark is stronger, it’s scale: while the data fits on one machine, pandas / DuckDB / SQL are faster and simpler; only when the data genuinely exceeds a single machine, or you need to scale out, do Spark’s distributed mechanics start to pay off.

Core concepts

ConceptWhat it is
Driverwhere your main program runs — creates the SparkSession, compiles your code into an execution plan (a DAG), schedules tasks
Executorthe processes scattered across cluster nodes that do the actual work, holding data partitions and running tasks
Cluster managerallocates resources and decides where executors start (YARN / Kubernetes / standalone)
RDDthe lowest layer, a “resilient distributed dataset” — fault-tolerant, recomputable
DataFramea distributed table with a schema (the mainstay of PySpark, and what you want); the Catalyst optimizer sits behind it
Partitionthe small chunks the data is cut into — the unit of parallel computation
Transformationfilter, select, groupBy, join… lazy, they only build the plan
Actioncount, show, collect, write… they trigger the whole plan to actually run
ShufflegroupBy, join and anything else that moves data across partitions — the most expensive operation there is

Architecture: what moves when your program runs

Driver SparkSession · DAG Cluster Manager YARN / K8s Executor tasks + partitions Executor tasks + partitions Executor tasks + partitions request resources
The driver breaks the plan into tasks, gets resources through the cluster manager, and hands them to the executors to run in parallel, one partition at a time

Lazy evaluation: transformation vs action

Spark’s most important mental model: the chain of transformations you write doesn’t run as you write it. It only accumulates a plan — a DAG of “how this should be computed” — and not until you call an action does Spark optimize the whole plan and throw it at the cluster.

read filter groupBy count transformation (lazy) transformation shuffle action triggers it
read → filter → groupBy only build the plan; calling count (an action) is what actually runs it; groupBy needs a shuffle in between (moving data across partitions)

A PySpark example

DataFrame: like working on one big table

from pyspark.sql import SparkSession
from pyspark.sql import functions as F

spark = SparkSession.builder.appName("sales").getOrCreate()

# read it in (swap for parquet / jdbc / kafka…)
df = spark.read.csv("orders.csv", header=True, inferSchema=True)

# a chain of transformations — all lazy, just accumulating a "plan"
daily = (
 df.filter(F.col("status") == "paid")
 .groupBy("date")
 .agg(F.sum("amount").alias("revenue"))
 .orderBy("date")
)

# an action — only this line fires the whole pipeline off on the cluster
daily.show()

filter, groupBy, agg and orderBy are all transformations — when execution reaches those lines, nothing has been computed yet; not until the daily.show() action does Spark hand the whole plan to Catalyst to optimize and then spread it over the executors.

RDD: seeing the line between transformation and action

rdd = spark.sparkContext.parallelize([1, 2, 3, 4, 5])

# transformations (lazy, just stacking up a plan)
doubled = rdd.map(lambda x: x * 2).filter(lambda x: x > 4)

# an action (this is what fires, and pulls the result back to the driver)
print(doubled.collect()) # [6, 8, 10]

map and filter return “a new RDD plan”, not data; collect() is what actually executes, and it pulls the result back to the driver (which is why collect() is dangerous on big data — you can blow up one driver’s memory with the whole cluster’s worth of rows).

Reflection

Most people’s “big data” isn’t that big

The most common misuse I see is reaching for Spark at a few hundred MB or a few GB. Distributed isn’t free: JVM startup, cluster scheduling, serialization, shuffling over the network — while the data still fits on one machine, those fixed costs dwarf the time they save. If it fits on one machine, use pandas / Polars / DuckDB / SQL: faster, and far easier to debug. Spark only starts paying off once the data genuinely exceeds a single machine, or you need to scale throughput out. Confirm the premise — the data really is too big for one box — before you shoulder that complexity. Same attitude I take to Airflow, and to any piece of infrastructure.

The cost you’re actually fighting is shuffle

Four fifths of performance instinct in Spark comes down to one thing: shuffle less. groupBy, join, distinct and the other wide dependencies drag data between nodes, and they’re the slowest part of any job. The moves that actually work in practice all circle it: filter early so there’s less data to compute on, use a broadcast join for a small table to avoid a big shuffle, and watch that your partition count is neither too high nor too low. Thinking shuffle through beats memorizing a pile of APIs.

Lazy evaluation cuts both ways

Laziness is what lets Spark optimize globally, and it drops beginners into two holes: first, you can’t tell which line is actually running when you debug (the answer is the action); second, calling several actions on the same pipeline (a count() then a show(), say) recomputes it from scratch every time — that’s when you want cache() / persist() to hold the intermediate result. Understanding the line between plan and execution is the precondition for writing Spark correctly.

Let someone else run it

Same as Airflow: running your own Spark cluster — resource scheduling, versions, tuning — is expensive. For most teams a managed platform (Databricks / EMR / Glue) works out better, and the energy goes into the data logic instead of babysitting a cluster. A tool is there to multiply what the team ships, not to hand you another operations debt.