03.1 Driver & Executors
Phase 3 — Apache Spark Fundamentals
The most important Spark runtime relationship is:
The driver coordinates; executors execute.
This distinction is essential for Databricks performance engineering. When a workload is slow or failing, you need to determine whether the bottleneck is the driver, executors, partitions, shuffle, storage, network, or execution plan.
1. The one-minute mental model
Spark Application | Driver plan / schedule / coordinate | +----+----+ | | Executor Executor ... Executor | | Tasks Tasks | | Partitions Partitions | | Data Data
In current Databricks architecture, a multi-node compute resource has one driver and one or more worker nodes. Databricks runs one Spark executor per worker node, so worker and executor are commonly used interchangeably in Databricks architecture.
Databricks — Compute configuration
2. What is the driver?
The driver is the coordinating process for a Spark application.
It is responsible for:
- Maintaining the Spark application context.
- Receiving notebook/job commands.
- Building and coordinating execution.
- Creating and scheduling jobs.
- Tracking stages and tasks.
- Communicating with executors.
- Maintaining notebook/application state.
- Returning results requested by the application.
Think of it as the orchestrator of one Spark application.
Notebook / Job | SparkSession | DRIVER | +-----+-----+ | | | Plan Schedule Monitor | Executors
The driver is not intended to process the entire dataset locally.
3. Driver versus distributed computation
Consider a transformation such as:
df.filter("amount > 100").groupBy("customer_id").sum("amount")
The driver does not normally pull the entire DataFrame into memory and calculate the result itself.
Instead, Spark builds an execution plan and distributes the work across executors.
Driver | | execution plan v Executors | +--> partition 1 +--> partition 2 +--> partition 3 +--> ...
This is the foundation of distributed processing.
4. What makes the driver a bottleneck?
A driver bottleneck occurs when too much work becomes driver-side work.
Common examples:
-
collect() -
toPandas() - Large Python objects
- Large result sets
- Excessive notebook state
- Very complex query plans
- Too many tiny jobs
- Excessive job submission
- Driver-side loops
- Heavy metadata processing
Example:
df.collect()
This asks Spark to return the complete result to the driver.
Safer for inspection:
df.limit(100).collect()
For production processing, keep the result distributed:
df.write.mode("overwrite").saveAsTable("catalog.schema.target")
Architect rule
Do not add workers to solve a driver bottleneck.
Workers increase executor capacity. They do not automatically increase driver memory or driver CPU.
Databricks notes that a larger driver can be appropriate when substantial data is collected back to the notebook/driver.
Databricks — Compute configuration
5. Driver memory
Driver memory can be consumed by:
- Spark application state
- Query planning
- Notebook state
- Metadata
- Result objects
- Python objects
- Collected data
- Driver-side libraries
| Symptom | Investigate |
|---|---|
Huge collect() | Driver |
Huge toPandas() | Driver |
| Huge query plan | Driver |
| Many notebooks attached | Driver state |
| Driver CPU very high | Driver workload |
| Executors mostly idle | Possible driver bottleneck |
A common mistake is increasing workers when the driver itself is under memory pressure.
6. Driver CPU
The driver also performs CPU work:
- Planning
- Scheduling
- Serialization
- Notebook/application logic
- Metadata processing
- Python code executed outside Spark
- Managing many jobs/tasks
A classic pattern is:
Driver CPU 90% Executor CPU 15% Workers 12
Adding workers here is usually the wrong first move.
Investigate:
- Driver-side Python.
- Large result collection.
- Complex execution plans.
- Excessive job submission.
- Metadata pressure.
- Many tiny tasks.
Databricks identifies long gaps in the Spark Jobs Timeline as potentially caused by an overloaded driver, complex plans, or non-Spark code that leaves workers idle.
Databricks — Gaps between Spark jobs
7. What is an executor?
An executor is a Spark process responsible for executing tasks for the application.
Executors perform distributed work such as:
-
Reading data
-
Filtering
-
Projection
-
Joins
-
Aggregations
-
Sorts
-
Shuffle
-
Caching/persistence
-
Writing output
Executor | +-- Task 1 -> Partition 1 +-- Task 2 -> Partition 2 +-- Task 3 -> Partition 3 +-- Task N -> Partition N
Executors are where most distributed data processing happens.
8. Worker node versus executor
This is a common interview question.
Worker node
The cloud compute machine/VM.
Executor
The Spark process running on that worker.
Worker Node | +-- Spark Executor +-- CPU cores +-- Memory +-- Local disk +-- Spark execution
Databricks runs one executor per worker node in its standard multi-node architecture.
Databricks — Compute configuration
Therefore:
Worker = infrastructure resource. Executor = Spark process using that resource.
9. Executor cores and task concurrency
Suppose each executor has eight usable execution cores.
Executor | +-- Core 1 -> Task +-- Core 2 -> Task +-- Core 3 -> Task +-- Core 4 -> Task +-- Core 5 -> Task +-- Core 6 -> Task +-- Core 7 -> Task +-- Core 8 -> Task
If you have four such executors:
4 executors x 8 cores approximately 32 concurrent task slots
This is a capacity model, not a guarantee of useful 32-way parallelism.
Useful parallelism also depends on:
- Number of partitions
- Stage dependencies
- Task duration
- CPU
- I/O
- Shuffle
- Skew
- Scheduling overhead
10. Why more cores do not always mean faster
Imagine:
Executors: 8 Cores: 8 each Partitions: 2
You cannot efficiently use all available task slots because the stage contains only two partitions.
Or:
Partitions: 800 One partition: extremely large 799 partitions: normal
Near the end of the stage, most executors can become idle while one task processes the oversized partition.
Therefore:
Compute capacity is useful only when the workload has enough balanced parallel work.
11. Executor memory
Executor memory supports distributed task execution.
Major consumers include:
- Aggregations
- Joins
- Sorts
- Shuffle
- Cached/persisted data
- Serialization
- JVM objects
- Python processing
- Execution state
Think:
Executor | +-- CPU +-- Execution memory +-- Storage/cache memory +-- Process overhead +-- Local disk
Do not assume all configured memory is available for arbitrary application objects.
12. Driver memory versus executor memory
Memorize this table:
| Workload pattern | Likely pressure |
|---|---|
Huge collect() | Driver |
Huge toPandas() | Driver |
| Complex plan | Driver |
| Notebook/application state | Driver |
| Large aggregation | Executor |
| Large join | Executor |
| Shuffle | Executor + network + local disk |
| Large cache | Executor |
| Skewed partition | One executor/task |
| Many parallel tasks | Executors |
The first diagnostic question should be:
Which process is under pressure?
13. Executor local disk and spill
Executor local disk is important for intermediate processing.
When memory is insufficient for an operation, Spark may spill intermediate data to disk.
Large sort / aggregation | v Memory fills | v Spill | v Local disk I/O | v Slower stage
High spill is not automatically a failure, but excessive spill is often a performance signal.
Databricks' current Spark UI guidance recommends checking skew and spill when diagnosing long stages.
Databricks — Spark UI guide
14. Memory pressure is not always an OOM
A workload can become unhealthy before it crashes:
More data | v Memory pressure | v GC increases | v Spill increases | v Disk I/O increases | v Stage gets slower | v Possible executor failure
Do not wait for OOM before investigating memory pressure.
15. Executor OOM
A common failure pattern is:
Task | v Memory pressure | v Executor lost | v Task retry | +---- success -> continue | +---- repeated failure -> stage/job failure
Databricks documents that when executors run out of memory, Spark removes them and retries their tasks; repeated failures can eventually fail the stage or job.
Databricks — Spark memory issues
Common causes include:
- Too few shuffle partitions
- Large broadcast
- UDF memory usage
- Window without suitable partitioning
- Data skew
- Large aggregation
- Oversized partitions
16. Do not immediately increase executor memory
Suppose an executor fails.
First investigate:
- Skew
- Partition size
- Broadcast joins
- Shuffle partitions
- UDFs
- Window functions
- Aggregation state
- Unnecessary cache
- Memory per core
Only then decide whether compute sizing is actually the correct fix.
Databricks recommends looking at memory-per-core when validating memory pressure rather than considering only total node memory.
Databricks — Spark memory issues
17. Driver OOM
Common causes:
collect()
result = df.collect()
toPandas()
pdf = df.toPandas()
Large Python objects
records = [row.asDict() for row in df.collect()]
The third example is particularly dangerous because it first collects data and then creates another large Python representation.
Better approach:
summary = ( df .groupBy("country") .count() )
Then collect only the small summary if necessary.
18. Why toPandas deserves special attention
The data flow is:
S3 / Delta | v Executors | v Small result | v Driver | v Pandas DataFrame
The pandas DataFrame lives on the driver-side Python process.
Therefore:
Use pandas for genuinely small results, not as a way to bypass distributed Spark processing.
19. Driver-side Python can leave workers idle
Consider:
for customer in customers: run_some_python_logic(customer)
If this is ordinary Python rather than Spark execution, workers may sit idle.
Databricks currently identifies non-Spark code as a cause of gaps in the Spark Jobs Timeline and notes that workers can remain idle while compute continues to incur cost.
Databricks — Gaps between Spark jobs
Architectural preference:
Python loop | v Ask: can Spark express this? | v Spark DataFrame / SQL | v Distributed execution
20. Driver ↔ executor communication
The driver schedules work and receives execution status.
DRIVER | | task scheduling / status | +--+-----------+-----------+ | | | v v v Executor 1 Executor 2 Executor N | | | +--------------+-----------+ | task results status/metrics
Communication overhead matters when:
- There are huge numbers of tiny tasks.
- Tasks are extremely short.
- Too many jobs are submitted.
- The driver is overloaded.
- The application creates excessive scheduling overhead.
This is one reason small files and tiny partitions can hurt performance even when the cluster has plenty of CPU.
21. Executor lifecycle
A simplified lifecycle:
Compute starts | Worker provisioned | Executor starts | Executor registers | Tasks execute | Tasks complete | Executor remains available | Compute terminates
With autoscaling, the number of workers/executors can change while a workload is running.
Databricks supports minimum and maximum worker settings for autoscaling.
Databricks — Compute configuration
22. Executor loss
Executors can disappear for different reasons:
- Memory failure
- Cloud instance interruption
- Autoscaling
- Network problems
- Runtime failure
- Infrastructure problems
Do not classify every executor loss as OOM.
Correct sequence:
Executor lost | Check Compute Event Log | +----+----------------+ | | v v Infrastructure event? No clear event | | v v Investigate cloud Spark UI / logs | v Memory / task / data distribution | v Root cause
Databricks recommends checking the compute Event Log first for executor failures, including autoscaling or spot/preemptible instance events.
Databricks — Failing jobs or executors removed
23. One slow executor: investigate skew
Suppose task durations look like:
Executor 1 42 sec Executor 2 44 sec Executor 3 41 sec Executor 4 47 sec Executor 5 43 sec Executor 6 8 min 31 sec
Do not immediately replace the machine.
Possible explanations:
- One oversized partition
- Data skew
- Spill
- GC
- Slow I/O
- Network issue
- Input imbalance
This is a classic straggler pattern.
24. Real scenario: driver bottleneck
Observed:
Driver CPU 90% Executor CPU 15% Shuffle Low Workers 12
Diagnosis:
The distributed compute is underutilized.
Investigate:
- Driver-side Python
-
collect() -
toPandas() - Complex query plan
- Metadata
- Excessive job submission
- Tiny jobs
- Notebook/application logic
Wrong fix:
Add 20 workers.
Better fix:
Remove or reduce driver-side work.
25. Real scenario: executor bottleneck
Observed:
Driver CPU 25% Executor CPU 90% Shuffle High Spill High Failed tasks Repeated
Investigate:
- Join strategy
- Aggregation
- Partition count
- Data skew
- Memory per core
- Broadcast size
- Input/output volume
Possible fixes depend on root cause:
- Repartition
- Fix skew
- Change join strategy
- Adjust adaptive behavior
- Avoid oversized broadcasts
- Increase memory per core
- Rewrite transformations
26. Spark UI: where to look
Databricks currently recommends:
- Start with the Jobs Timeline.
- Identify the longest stage.
- Look for skew or spill.
- Determine whether the stage is I/O bound.
- Investigate other causes of long runtime.
Useful signals include:
- Executor count
- Active tasks
- Failed tasks
- Task duration
- Shuffle read
- Shuffle write
- Spill
- GC time
- Input/output
- Executor duration
Databricks — Diagnose cost and performance issues using Spark UI
Apache Spark's Web UI also exposes executor resource usage, task counts, shuffle totals, storage memory and GC-related signals.
Apache Spark — Web UI
27. Driver logs versus executor logs
Driver logs
Useful for:
- Application exceptions
- Planning/scheduling issues
- Driver-side failures
- Notebook/application output
- Streaming startup problems
- Driver hangs
Executor logs
Useful for:
- Task-specific failures
- Executor exceptions
- UDF failures
- Worker-side processing problems
- Executor-specific errors
Databricks documents driver and executor logs as separate troubleshooting sources. Log access depends on compute access mode and permissions.
Databricks — Debugging with Spark UI
Also:
Never print credentials, tokens, or secrets into driver logs.
Databricks warns that sensitive values placed in Spark configuration properties or environment variables can be exposed through driver logs.
Databricks — Secret configuration guidance
28. Thread dumps
A thread dump is a snapshot of thread states.
It can help investigate:
- Hanging driver
- Hanging executor
- Long-running task
- Blocking operations
- Thread contention
The Spark UI exposes thread-dump information from the Executors view.
Databricks — Debugging with Spark UI
Think of it as:
“What are the JVM threads doing right now?”
29. Hands-on lab — inspect the driver
Run:
print("Spark version:", spark.version) print("Application ID:", spark.sparkContext.applicationId) print("Master:", spark.sparkContext.master) print("Application name:", spark.sparkContext.appName)
Then open the Spark UI.
Record:
| Metric | Your observation |
|---|---|
| Application ID | |
| Driver | |
| Workers | |
| Executors | |
| Jobs | |
| Stages |
The goal is to connect:
Notebook → SparkSession → Driver → Executors → Tasks
30. Hands-on lab — distributed execution
Create a dataset:
df = spark.range(0, 10_000_000)
print("Partitions:", df.rdd.getNumPartitions())
Run:
result = ( df .filter("id % 2 = 0") .groupBy() .count() )
result.show()
Open the Spark UI and find:
- Job
- Stage
- Tasks
- Executors
- Duration
- Input/output
Do not optimize it yet.
First learn to observe execution.
31. Hands-on lab — driver collection
Compare:
small = df.limit(100).collect() print(len(small))
with the conceptual danger of:
Do NOT run blindly on a large dataset.
df.collect()
Question:
Where does the returned result go?
Answer:
The driver/application process.
32. Hands-on lab — classify the bottleneck
For a workload you know is slow, capture:
| Metric | Observation |
|---|---|
| Driver CPU | |
| Driver memory | |
| Executor CPU | |
| Executor memory | |
| Executor count | |
| Failed tasks | |
| Shuffle read | |
| Shuffle write | |
| Spill | |
| Longest stage | |
| Longest task |
Then classify:
Driver Executor Shuffle Skew I/O Scheduling Compute sizing
This is the beginning of professional Spark performance engineering.
33. Architect decision tree
Slow workload
|
Is driver busy?
/
YES NO
| |
Driver Executors
| |
plan / CPU / memory
collect / shuffle / spill
Python
| |
+----+-----+
|
Root cause
|
Targeted change
The key principle:
Diagnose first. Resize second.
34. Common anti-patterns
Anti-pattern 1 — collect everything
df.collect()
Risk: driver memory pressure.
Anti-pattern 2 — convert a huge DataFrame to pandas
df.toPandas()
Risk: driver memory pressure and loss of distributed processing.
Anti-pattern 3 — add workers without checking the bottleneck
Risk: higher cost with little or no performance improvement.
Anti-pattern 4 — increase executor memory without investigating skew
Risk: paying for larger machines while the real issue is one oversized partition.
Anti-pattern 5 — use Python loops for distributed transformations
Risk: workers remain underutilized while the driver performs local work.
35. Architect sizing model
Do not think:
Bigger cluster = faster
Think:
Performance
parallelism × efficient execution × compute capacity × good data layout
Subject to:
- Driver capacity
- Executor memory
- CPU
- Network
- Storage I/O
- Shuffle
- Partitioning
- Skew
- Query plan
This model will be reused throughout the Performance Engineering phase.
36. Production checklist
Driver
- Avoid unbounded
collect() - Avoid unbounded
toPandas() - Monitor CPU and memory
- Control notebook/application state
- Avoid excessive job submission
- Investigate complex plans
Executors
- Appropriate memory per core
- Appropriate worker type
- Enough partitions
- No severe skew
- No excessive spill
- Reasonable shuffle
- Appropriate cache usage
Compute
- Correct compute type
- Sensible autoscaling
- Appropriate min/max workers
- Appropriate driver size
- Cost guardrails
37. Architect-level interview questions
Q1. What is the driver?
The process that coordinates a Spark application, builds/coordinates execution and schedules work.
Q2. What is an executor?
A Spark process that executes tasks against data partitions.
Q3. What is the difference between worker and executor?
A worker is the underlying compute node; an executor is the Spark process running on that node.
Q4. Why can collect() cause driver OOM?
Because the result is returned to the driver.
Q5. Why does adding workers not fix driver OOM?
Workers provide executor capacity. Driver memory is a separate resource.
Q6. Why can one executor be much slower than the others?
Skew, an oversized partition, spill, GC, I/O or infrastructure issues can create a straggler.
Q7. How do you investigate executor failure?
Check the compute Event Log, Spark UI, failed tasks and executor logs, then classify the cause as infrastructure, memory, data distribution or execution-related.
Q8. What is the first place to look for a slow Spark job?
The Jobs Timeline and longest stage, followed by skew, spill and I/O characteristics.
Q9. When should you increase driver size?
After proving the driver is the bottleneck and the workload genuinely needs more driver CPU or memory.
Q10. When should you increase executor memory?
After proving distributed execution is memory-bound and the issue is not better solved by fixing skew, partitioning, joins, aggregations or another design change.
38. Final mental model
USER / JOB | SPARKSESSION | DRIVER plan / schedule / monitor | distributed tasks | +----+----+----+ | | | | v v v v E1 E2 E3 EN | | | | tasks tasks tasks tasks | | | | partitions / data | STORAGE S3 / Delta
Four rules to remember
Driver problem → investigate driver.
Executor problem → investigate distributed execution.
One slow task → investigate partitioning and skew.
Repeated executor loss → investigate Event Log, memory and infrastructure.
If these four rules become instinctive, the next Spark topics become much easier.
Official references
- Databricks — Compute configuration
- Databricks — Spark UI guide
- Databricks — Spark memory issues
- Databricks — Failing jobs or executors removed
- Apache Spark — Web UI
Next
03.2 Jobs, Stages & Tasks — how Spark turns an action into a job, divides it into stages, creates tasks from partitions, and uses dependency/shuffle boundaries to build the execution graph.