Apache Spark Architecture
Spark follows a master/worker architecture with a central driver coordinating distributed executors. Understanding this architecture is essential for tuning performance and diagnosing failures.
High-Level Architecture
User Code (PySpark / Scala) โ DRIVER PROGRAM โโโ SparkContext / SparkSession โโโ DAGScheduler โ builds stage DAG from transformation graph โโโ TaskScheduler โ assigns tasks to executor cores
โ (via Cluster Manager)
CLUSTER MANAGER (YARN / Kubernetes / Standalone) โโโ Negotiates resources โโโ Launches executor processes on Worker Nodes
WORKER NODE 1 WORKER NODE 2 WORKER NODE Nโโโ Executor โโโ Executor โโโ Executor โโโ Task 1 โโโ Task 5 โโโ Task 9 โโโ Task 2 โโโ Task 6 โโโ Task 10 โโโ JVM memory โโโ JVM memory โโโ JVM memory โโโ Block Manager โโโ Block Manager โโโ Block ManagerThe Driver
The driver is the JVM process running the main() function (or the PySpark script). It:
- Creates the
SparkSession/SparkContext - Analyzes user code to build the execution DAG
- Breaks the DAG into stages and tasks
- Schedules tasks on executors
- Collects results from executors for
collect()/count()actions
from pyspark.sql import SparkSession
# This process IS the driverspark = SparkSession.builder.appName("MyApp").getOrCreate()sc = spark.sparkContext
# All planning happens here; data doesn't pass through the driver (except collect())df = spark.read.parquet("s3://bucket/data/")result = df.groupBy("region").sum("revenue")
# Only this line brings data to the driver โ keep result smallresult.show(20)Executors
Executors are long-running JVM processes on worker nodes. Each executor:
- Receives serialized tasks from the driver
- Runs tasks in parallel across its CPU cores
- Stores cached RDD/DataFrame partitions (Block Manager)
- Returns task results and shuffle data
# Tune executor resources at session creationspark = SparkSession.builder \ .config("spark.executor.instances", "10") # 10 executor JVMs .config("spark.executor.cores", "4") # 4 cores per executor .config("spark.executor.memory", "8g") # 8 GB JVM heap per executor .config("spark.executor.memoryOverhead", "2g") # Off-heap (shuffle, native) .getOrCreate()Cluster Managers
Spark delegates resource allocation to a cluster manager:
| Cluster Manager | Use Case |
|---|---|
| Standalone | Simple Spark-only clusters; dev/test |
| YARN | Shared Hadoop clusters; Hadoop ecosystem integration |
| Kubernetes | Cloud-native deployments; container isolation per job |
| Mesos | Legacy; largely replaced by Kubernetes |
| Local | Development (local[*] uses all CPU cores) |
# Run locally on 4 coresspark = SparkSession.builder.master("local[4]").getOrCreate()
# Submit to YARN# spark-submit --master yarn --deploy-mode cluster my_app.py
# Submit to Kubernetes# spark-submit --master k8s://https://k8s-cluster:6443 my_app.pyScheduler Components
Action called โRDD Graph / DataFrame plan โDAGScheduler - Builds DAG - Identifies shuffle boundaries โ creates stages - Submits stages with resolved dependencies โTaskScheduler - Takes stages from DAGScheduler - Creates one Task per partition - Assigns tasks to available executor slots - Handles task failures and retries โSchedulerBackend - Communicates with Cluster Manager to acquire/release resources - Launches executor processesMemory Layout per Executor
Executor JVM Heap (e.g., 8 GB)โโโ Reserved Memory: 300 MB (system use, not configurable)โโโ User Memory (40%): 3.1 GBโ โโโ UDFs, data structures defined in user codeโโโ Unified Memory (60%): 4.6 GB โโโ Execution Memory (dynamic): shuffle, sort, aggregation โโโ Storage Memory (dynamic): cached RDDs/DataFramesExecution and storage memory share the unified pool dynamically via spark.memory.fraction (default: 0.6) and spark.memory.storageFraction (default: 0.5).