Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Courses›PySpark›Apache Spark Architecture — Driver, Executor, Cluster

Spark Fundamentals

Apache Spark Architecture — Driver, Executor, Cluster

Spark architecture is a distributed processing framework designed to decouple the master control logic from the actual data computation tasks. Understanding these roles is crucial for debugging performance bottlenecks, optimizing memory allocation, and scaling workloads across clusters. You should leverage this knowledge when designing pipelines to ensure that heavy transformations are pushed to executors while maintaining a lean driver state.

The Driver: The Command Center

The Driver is the primary process where the SparkContext lives, serving as the brain of your application. When you submit a script, the Driver converts your high-level transformations into a Directed Acyclic Graph (DAG) of logical operations. It then breaks this graph into physical stages and tasks to be distributed across the cluster. If your driver fails, the entire application fails, which is why it is critical to avoid collecting massive datasets into the driver's memory using methods like collect(). If you attempt to aggregate millions of rows into a single list on the driver, you will trigger an OutOfMemoryError. Always keep the driver lightweight, using it only for coordination and lightweight metadata management rather than heavy data crunching. The driver communicates with the cluster manager to request resources, ensuring that the work is orchestrated effectively across the available pool of workers.

from pyspark.sql import SparkSession

# The SparkSession acts as the entry point, residing in the Driver
spark = SparkSession.builder.appName("DriverDemo").getOrCreate()

# The driver orchestrates this operation; do not collect massive results
# data = spark.read.parquet("large_data").collect() # Dangerous!
# Instead, keep data on workers and save directly to distributed storage
spark.read.parquet("large_data").write.parquet("output")

The Executor: The Compute Engine

Executors are worker nodes' JVM processes responsible for running the actual tasks assigned by the Driver. Each executor runs multiple threads to handle tasks in parallel, maximizing the utilization of the underlying hardware cores. They also serve a second, vital purpose: caching data. When you call persist() or cache() on a DataFrame, the executor stores blocks of that data in its local heap or disk, drastically speeding up subsequent iterations of your algorithm. Because executors hold the data, the network overhead is minimized during computations. If you notice specific tasks are running slower than others, it is often a sign of data skew within an executor. By understanding that each executor has its own memory footprint, you can tune settings like spark.executor.memory to avoid garbage collection thrashing. Properly sized executors ensure that the load is evenly distributed across your cluster, preventing individual machines from becoming performance bottlenecks.

from pyspark.sql.functions import col

# Persisting data forces executors to cache the blocks in their memory
df = spark.read.table("large_table").persist()

# Subsequent actions will now hit the executor local cache rather than re-reading
result = df.filter(col("id") > 1000).count()

The Cluster Manager: Resource Allocation

The Cluster Manager is an external service responsible for managing the resources of the physical or virtual machines in your environment. It acts as the bridge between your Spark application and the available hardware. When your driver asks for CPU cores and RAM to process a massive join, it is the Cluster Manager that grants these resources by spawning executors. It decides which nodes are free to receive new tasks and monitors the health of the entire cluster. By abstracting the hardware, the Cluster Manager allows your Spark code to remain portable; you can run the exact same logic on a single development machine or a cluster of hundreds of nodes. If your application crashes due to a lack of resources, the Cluster Manager is the entity that rejects your requests for additional worker slots. Understanding this interaction helps you estimate hardware requirements accurately before launching production jobs.

# The cluster manager is configured during session initialization
# The master URL tells the Driver how to reach the cluster manager
spark = SparkSession.builder \
    .master("yarn") \
    .config("spark.executor.instances", "10") \
    .config("spark.executor.memory", "4g") \
    .getOrCreate()

Tasks and Stages: Breaking Down the Work

When a Spark job is triggered, the Driver breaks the logic into stages based on wide dependencies, such as shuffles. A stage consists of multiple tasks, where each task is a unit of work that processes a single partition of data. This allows for massive parallelism: if you have a thousand partitions and a hundred executor cores, Spark will queue the tasks and execute them as cores become available. The efficiency of your job depends on the balance between these partitions and the executor capacity. Too few tasks result in underutilized resources, while too many tasks introduce overhead due to task scheduling latency. By reasoning about how your data is partitioned, you control how the executors interact with the information. When a task finishes, it reports back to the Driver, which tracks progress and prepares for the next stage. This tight coordination ensures that complex operations like group-by or sort are executed reliably across the distributed network.

# We can control task distribution via repartitioning
# This creates 200 tasks for the following operation
data = spark.read.csv("input_data").repartition(200)

# The Driver coordinates these 200 tasks across the cluster's executors
final_df = data.groupBy("category").sum("value")

Data Locality and Shuffling

Data locality is a critical performance concept where Spark attempts to run tasks on the nodes where the data already resides. Moving large datasets across the network to an executor is significantly slower than processing it locally. When you perform operations that require moving data between nodes—a process called shuffling—the performance often drops. Shuffling involves writing the intermediate results of a stage to local disk, transmitting them across the network, and reading them on another executor. Recognizing when a shuffle occurs is the key to writing performant Spark code. Operations like joins or distinct counts across keys usually trigger wide shuffles. To minimize this, we often use broadcast joins for smaller tables. By forcing the smaller dataset to be sent to every executor in advance, we avoid the heavy network shuffle, keeping the data movement local to the executor during the processing phase. Managing your shuffling effectively is how you scale from minutes to seconds in execution time.

from pyspark.sql.functions import broadcast

# Large table
fact_table = spark.read.parquet("fact_table")
# Small dimension table
dimen_table = spark.read.parquet("dim_table")

# Broadcasting forces the small table to stay local, avoiding a shuffle
result = fact_table.join(broadcast(dimen_table), "id")

Key points

  • The Driver process acts as the master node responsible for high-level orchestration and scheduling.
  • Executors are distributed processes running on worker nodes that handle computation and data storage.
  • The Cluster Manager manages physical resource allocation for the entire Spark application lifecycle.
  • Spark breaks logical queries into DAGs, which are further divided into stages and individual tasks.
  • Data locality is prioritized to reduce network latency during distributed compute cycles.
  • Shuffling is a resource-intensive operation that occurs when data must be moved across different executors.
  • Caching intermediate results in executor memory is a primary technique for accelerating iterative algorithms.
  • Proper partitioning strategies are essential for balancing load across the available executor cores.

Common mistakes

  • Mistake: Thinking the Driver node performs the heavy data processing. Why it's wrong: The Driver's role is to coordinate and schedule tasks, not execute them. Fix: Delegate heavy computations to Executors.
  • Mistake: Misconfiguring the number of executors in cluster mode. Why it's wrong: Too few executors lead to under-utilization, while too many can cause resource contention. Fix: Size executors based on the specific memory and CPU needs of your workload.
  • Mistake: Assuming data stays on the Driver when calling collect(). Why it's wrong: collect() pulls data from all executors to the Driver, which can crash the Driver if the dataset exceeds its memory limit. Fix: Use take() or show() for inspection.
  • Mistake: Treating local mode as identical to cluster mode. Why it's wrong: Local mode shares JVM resources; cluster mode distributes tasks across physical machines, impacting serialization and data locality. Fix: Always test in a distributed environment.
  • Mistake: Ignoring task serialization overhead. Why it's wrong: Sending too many small tasks or complex closures from the Driver to Executors causes high latency. Fix: Broadcast large read-only variables to executors.

Interview questions

What is the role of the Driver program in a PySpark application?

The Driver is the heart of every PySpark application. It hosts the SparkContext, which is responsible for converting the user's code into a logical plan and subsequently a physical execution plan. The Driver manages the scheduling of tasks across the executors and keeps track of the lineage of RDDs or DataFrames. Essentially, the Driver acts as the command center, ensuring that the application logic is decomposed into smaller tasks that can be executed in parallel across the cluster nodes, ultimately collecting the final results back to the master node.

How does a PySpark Executor contribute to the architecture?

An Executor is a worker process launched on a cluster node that is responsible for running the tasks assigned to it by the Driver. Unlike the Driver, which handles metadata and coordination, the Executor's primary role is to execute the actual computational logic and store data in memory or on disk when caching is required. Each executor has a specific number of slots, allowing multiple tasks to run in parallel. By localizing data processing, the executor minimizes network traffic, which is critical for maintaining performance in large-scale data processing workflows.

Explain the interaction between the Driver and the Cluster Manager when launching a job.

When you submit a PySpark application, the Driver requests resources from the Cluster Manager, such as YARN, Mesos, or the standalone Spark scheduler. The Cluster Manager allocates the requested number of executor processes on the worker nodes. Once these executors are up and running, they register themselves with the Driver. The Driver then partitions the data and ships the code to the executors to perform the transformation and action operations. This separation of concerns allows the Cluster Manager to handle resource abstraction while the Driver remains focused on task orchestration.

Compare the 'Client' mode and 'Cluster' mode deployment in PySpark architecture.

In Client mode, the Driver process runs on the machine where the spark-submit command was executed, which is typically your edge node or local laptop. This is ideal for interactive debugging. In Cluster mode, the Driver runs inside the cluster itself, usually as an ApplicationMaster. Cluster mode is much more robust for production environments because if the machine you submitted the job from goes offline, the application continues to run. Choosing between them depends on whether you prioritize interactive feedback or long-running, fault-tolerant production execution.

What happens in PySpark when an action is triggered in the context of the Directed Acyclic Graph (DAG)?

When an action like .collect(), .count(), or .write() is called, the PySpark Driver transforms the logical plan into a DAG of tasks. It identifies the optimal stages of execution by looking for shuffle boundaries, where data must be reorganized across the cluster. The Spark Scheduler then splits these stages into individual tasks and distributes them to the executors. The executors compute the data locally, and if a shuffle is required, they exchange partitions. The final results are returned to the Driver only after all transformations in the chain are successfully computed.

How does the partitioning strategy within an Executor affect PySpark memory management and performance?

Partitioning is the fundamental unit of parallelism in PySpark. When data is unevenly partitioned, an executor might face 'data skew,' where one core performs significantly more work than others, leading to idle resources elsewhere. To optimize, use 'repartition()' or 'coalesce()'. For example, calling `df.repartition(100)` forces the Driver to redistribute data across the cluster executors. Proper partitioning ensures that memory is used efficiently, preventing OutOfMemory errors on individual executors and maximizing the throughput of the cluster by ensuring all available CPU cores are consistently utilized throughout the entire execution lifecycle.

All PySpark interview questions →

Check yourself

1. When a PySpark application runs, what is the primary responsibility of the Driver process?

  • A.Processing the actual data records in parallel
  • B.Converting the logical plan into a physical execution plan and scheduling tasks
  • C.Managing the storage of data frames on disk for all executors
  • D.Handling the direct read operations from all source databases simultaneously
Show answer

B. Converting the logical plan into a physical execution plan and scheduling tasks
The Driver converts the high-level code into a DAG, optimizes it, and schedules tasks. It does not process actual data (Option 0), manage disk storage (Option 2), or handle direct DB reads (Option 3); those are the tasks of executors and the underlying I/O layers.

2. What happens if you use a broadcast variable correctly in PySpark?

  • A.It forces the Driver to copy the entire dataset to every partition
  • B.It caches the data in memory on every executor, reducing the need for shuffle operations
  • C.It forces the cluster to restart because broadcast variables are forbidden in production
  • D.It creates a dedicated executor just to manage the shared data variable
Show answer

B. It caches the data in memory on every executor, reducing the need for shuffle operations
Broadcast variables allow executors to access a read-only variable locally without shuffling it across the network. Option 0 is wrong because the Driver does not 'copy to partitions', Option 2 is incorrect, and Option 3 describes a mechanism that does not exist.

3. Why might a PySpark job fail with an OutOfMemoryError on the Driver even when executors have plenty of memory?

  • A.Because the Driver is responsible for storing all task metadata
  • B.Because the user called collect() on a massive dataset that cannot fit into the Driver's memory
  • C.Because the executors are trying to write their local state back to the Driver
  • D.Because the Driver is automatically caching every dataframe that is created
Show answer

B. Because the user called collect() on a massive dataset that cannot fit into the Driver's memory
collect() gathers results from all executors into the Driver's memory. If the data is larger than the Driver's heap, it crashes. The other options describe behaviors that do not cause memory pressure on the Driver.

4. In the context of the Spark execution model, what is a 'Stage'?

  • A.A sequence of tasks that can be performed without requiring a data shuffle
  • B.The physical hardware grouping of multiple executors
  • C.A list of all variables declared by the user in the PySpark script
  • D.The time it takes for an executor to heartbeat to the cluster manager
Show answer

A. A sequence of tasks that can be performed without requiring a data shuffle
Stages are defined by shuffle boundaries. A stage is a set of tasks that can run in parallel without a shuffle. Hardware grouping (1), variables (2), and heartbeats (3) are unrelated to the definition of a Spark stage.

5. How does the Cluster Manager interact with the Driver and Executors?

  • A.It processes the PySpark logic directly to save memory
  • B.It allocates resources for the Driver and starts the Executors on worker nodes
  • C.It replaces the need for the Driver by managing task scheduling itself
  • D.It serializes the final results into a SQL database for the user
Show answer

B. It allocates resources for the Driver and starts the Executors on worker nodes
The Cluster Manager (like YARN or K8s) handles resource requests. It starts the executors and provides the environment for the Driver. It does not run logic (0), replace the Driver (2), or handle serialization to SQL (3).

Take the full PySpark quiz →

Next →RDDs — Resilient Distributed Datasets

PySpark

26 lessons, free to read.

All lessons →

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app