Apache Spark is designed to process massive datasets across distributed machines. But distributed processing creates an interesting problem:
How can executors report information back to the driver without repeatedly returning entire datasets?
This is where Spark Accumulators come in.
Accumulators allow tasks running on executors to contribute values to a shared variable that can be read by the driver. They are particularly useful for counters and diagnostic information, such as counting invalid records, tracking skipped rows, or measuring how many records satisfy a condition.
In this tutorial, you’ll learn:
- What Spark accumulators are
- Why accumulators are needed
- How accumulators work
- How to create and use them in PySpark
- Built-in accumulators
- Custom accumulators
- Accumulators vs broadcast variables
- Common production pitfalls
- Why accumulators should generally not be used for business logic
- Interview questions around accumulators
Follow me and subscribe by email if you want practical Data Engineering, PySpark, Spark, Databricks, and SQL tutorials delivered as I publish them.
📚 Complete Apache Spark Tutorial Progress
APACHE SPARK LEARNING PATH
- ✓ What is Apache Spark?
- ✓ Why Spark is Faster than Hadoop
- ✓ Spark Architecture
- ✓ Driver vs Executor
- ✓ Cluster Managers
- → RDD vs DataFrame vs Dataset
- → SparkSession
- ✓ Reading Data
- ✓ Lazy Evaluation
- ✓ DAG
- ✓ Stages and Tasks
- ✓ Shuffle
- ✓ Narrow vs Wide Transformations
- → Writing Data
- → Data Transformations
- →Window Functions
- Aggregations
- UDFs
- Broadcast variables
1. What Are Accumulators in Spark?
An accumulator is a variable that tasks running on Spark executors can add to, while the driver can read the accumulated value.
The key idea is:
Executors can update an accumulator, but the driver reads its final value.
For example, imagine you’re processing 1 billion transaction records and want to count how many records have invalid transaction IDs.
You don’t necessarily want to collect those billion records back to the driver.
Instead, you can maintain a counter:
Driver │ │ accumulator = 0 │ ├───────────────┐ │ │ ▼ ▼Executor 1 Executor 2 │ │ │ +100 │ +250 │ │ └───────────────┬ ▼ Accumulator │ ▼ Driver = 350
Each executor contributes to the accumulator, and the driver can read the accumulated result.
2. Why Do We Need Accumulators?
Spark applications are distributed.
Suppose you have:
10 billion records ↓100 partitions ↓100 tasks
You want to count records that fail validation.
A naive approach might try to send information from every task back to the driver.
That isn’t how Spark is intended to handle distributed computation.
Instead, an accumulator allows each task to update a shared counter.
For example:
invalid_records = spark.sparkContext.accumulator(0)
Then:
def validate(record): global invalid_records if record is None: invalid_records += 1
The driver can later inspect:
print(invalid_records.value)
3. Accumulators Follow a One-Way Communication Model
This is one of the most important concepts.
The normal flow of Spark computation is:
Driver ↓Tasks ↓Executors
Accumulators allow information to flow back in a controlled way:
Driver ↓Tasks ↓Executors ↓Accumulator updates ↓Driver reads value
But executors should not use an accumulator as a mechanism for communicating arbitrary state back to the driver.
Accumulators are intended for aggregation, particularly counters and diagnostic information.
4. Creating an Accumulator in PySpark
The classic PySpark API provides SparkContext.accumulator().
For example:
invalid_records = spark.sparkContext.accumulator(0)
Now:
invalid_records = 0
Tasks can increment it:
invalid_records += 1
The driver can read it:
print(invalid_records.value)
5. Simple Example
Suppose we have:
numbers = spark.sparkContext.parallelize( [1, 2, 3, 4, 5])
Create an accumulator:
total = spark.sparkContext.accumulator(0)
Then:
def add_number(x): global total total += xnumbers.foreach(add_number)
Now:
print(total.value)
The accumulated value will be:
15
The important part is that the addition happened on executor-side tasks, while the driver reads the final accumulated value.
6. Accumulators and Lazy Evaluation
This is an important Spark concept.
Consider:
total = spark.sparkContext.accumulator(0)numbers.map(lambda x: total.add(x))
The accumulator may not be updated immediately.
Why?
Because map() is a transformation.
Spark uses lazy evaluation.
The transformation creates a plan but doesn’t necessarily execute the computation immediately.
An action such as:
result = numbers.collect()
or:
numbers.count()
causes the computation to execute.
This connects directly to the earlier tutorial:
Lazy Evaluation in Apache Spark →
The important mental model is:
Transformation ↓No immediate execution ↓Action ↓Task execution ↓Accumulator updates
7. Accumulators and Actions
Accumulators are primarily useful when Spark actually executes tasks.
For example:
counter = spark.sparkContext.accumulator(0)df.rdd.foreach( lambda row: counter.add(1))
The foreach() action triggers execution.
Then:
print(counter.value)
can be used by the driver to inspect the accumulated result.
8. Example: Count Invalid Records
Suppose you’re processing customer records:
customer_idnameemail
You want to count invalid records.
invalid_count = spark.sparkContext.accumulator(0)def validate(row): global invalid_count if row.customer_id is None: invalid_count += 1 return row
Apply it:
validated = df.rdd.map(validate)
Trigger execution:
validated.count()
Then:
print( "Invalid records:", invalid_count.value)
This is a reasonable diagnostic use case.
9. Accumulators for Data Quality Monitoring
Accumulators can be useful for certain data-quality metrics.
For example:
Total recordsInvalid recordsMissing IDsMalformed recordsRejected records
You could maintain separate counters:
missing_id = spark.sparkContext.accumulator(0)invalid_email = spark.sparkContext.accumulator(0)invalid_amount = spark.sparkContext.accumulator(0)
During processing:
if row.customer_id is None: missing_id += 1if not valid_email(row.email): invalid_email += 1if row.amount < 0: invalid_amount += 1
At the driver:
print(missing_id.value)print(invalid_email.value)print(invalid_amount.value)
This can provide useful diagnostic information.
10. Accumulators Are Not a Replacement for Aggregations
This is a critical distinction.
Suppose you want to calculate:
Total sales by customer
You should not use an accumulator.
Instead, use Spark’s distributed aggregation capabilities:
df.groupBy("customer_id").sum("amount")
Why?
Because this is a data-processing requirement.
Spark’s DataFrame and SQL APIs are designed to perform distributed aggregations efficiently.
Accumulators are better suited to:
CountersDiagnosticsMonitoringDebugging
rather than producing your primary business dataset.
11. Accumulator vs DataFrame Aggregation
Consider two different requirements.
Requirement A
Count how many records have a missing customer ID.
An accumulator can be useful for diagnostics.
Requirement B
Calculate total sales for every customer.
Use:
df.groupBy("customer_id").sum("amount")
not:
accumulator
The distinction is:
Accumulator→ Side-effect / diagnostic informationDataFrame aggregation→ Business result
12. Accumulators and Fault Tolerance
This is where accumulators become tricky.
Spark can retry tasks when failures occur.
For example:
Task 5 ↓Fails ↓Retry ↓Task 5 executes again
If the task updates an accumulator before failing, you need to understand that task retries can affect accumulator updates.
Spark’s documentation specifically warns that accumulator updates made inside transformations can be problematic because a transformation may execute multiple times due to retries or recomputation. (spark.apache.org)
This leads to an important production rule:
Do not rely on accumulator values as the authoritative source for critical business logic.
13. Why Accumulators Should Not Drive Business Logic
Imagine your application does this:
if invalid_count.value > 1000: stop_pipeline()
This can be dangerous if the accumulator is affected by retries or recomputation.
Instead, calculate the business metric using Spark’s normal distributed operations.
For example:
invalid_records = df.filter( "customer_id IS NULL")count = invalid_records.count()
Now the result is an explicit Spark computation rather than relying on an executor-side side effect.
14. Accumulators and Task Retries
Imagine:
Partition 1 ↓Task runs ↓Accumulator +100 ↓Task fails ↓Task retries ↓Accumulator +100
Depending on where and how the accumulator is used, you need to be careful about interpreting the final value.
Spark provides guarantees around accumulator updates for certain execution contexts, but transformations and recomputation make accumulator-based business logic unsafe.
This is why accumulators are primarily recommended for monitoring and debugging rather than correctness-critical calculations.
15. Accumulators and Spark UI
Accumulators can also be associated with Spark’s monitoring mechanisms.
Spark’s web UI can expose accumulator information associated with tasks and stages.
This can be useful when troubleshooting distributed applications.
For example:
Spark UI ↓Job ↓Stage ↓Task ↓Accumulator metrics
If you’re troubleshooting a production Spark application, the Spark UI is one of the most useful tools to understand what happened during execution.
This connects directly to:
Spark Architecture →
and:
Stages and Tasks →
16. Accumulators vs Broadcast Variables
These two concepts are commonly asked together in interviews.
They solve opposite problems.
Broadcast Variable
Used to send data:
Driver ↓Executors
Example:
bc = spark.sparkContext.broadcast(lookup)
Accumulator
Used to aggregate information:
Executors ↓Driver
Example:
counter = spark.sparkContext.accumulator(0)
Think of them like this:
Broadcast ↓Driver → ExecutorsAccumulator ↑Driver ← Executors
17. Broadcast vs Accumulator
| Feature | Broadcast Variable | Accumulator |
|---|---|---|
| Main purpose | Distribute shared data | Aggregate values |
| Direction | Driver → Executors | Executors → Driver |
| Executor access | Read | Update |
| Driver access | Read | Read |
| Typical use | Lookup data | Counters |
| Mutable by tasks? | No | Add/update |
| Example | Country lookup | Invalid record count |
This distinction is one of the most useful things to remember for Spark interviews.
18. Accumulators vs Cache
These are also completely different concepts.
Cache
df.cache()
Purpose:
Reuse previously computed distributed data.
Accumulator
counter = spark.sparkContext.accumulator(0)
Purpose:
Collect an aggregated diagnostic value from task execution.
So:
Cache→ Store/reuse computation resultsAccumulator→ Track aggregated information
19. Custom Accumulators
Spark also supports accumulator types beyond simple numeric counters through accumulator APIs.
However, for modern PySpark applications, you should generally prefer DataFrame/Spark SQL operations for data processing and use accumulator functionality primarily where it is genuinely appropriate.
The exact accumulator APIs have evolved across Spark versions, so always check the documentation for the Spark version you’re running.
20. A Production Example
Imagine a 2 TB ETL pipeline:
Raw Data ↓Validation ↓Transformation ↓Enrichment ↓Delta Table
During validation, you want to know:
Records processedRecords rejectedRecords with missing IDsRecords with invalid dates
You could use counters for diagnostic purposes:
missing_id = spark.sparkContext.accumulator(0)invalid_date = spark.sparkContext.accumulator(0)
Then:
def validate(row): if row.customer_id is None: missing_id += 1 if not valid_date(row.transaction_date): invalid_date += 1 return row
After the job:
print("Missing IDs:", missing_id.value)print("Invalid dates:", invalid_date.value)
However, for production data-quality reporting, you may instead want to persist rejected records and calculate metrics using Spark SQL/DataFrame operations.
For example:
Valid Records ↓Delta TableInvalid Records ↓Quarantine Delta TableMetrics ↓Data Quality Dashboard
This provides a more durable and auditable design.
21. Common Mistakes
Mistake 1: Using accumulators for business results
Don’t use an accumulator to calculate your primary analytical dataset.
Use DataFrame or SQL operations.
Mistake 2: Assuming accumulators behave like normal variables
They don’t.
Spark executes code across distributed tasks, and task retries/recomputation affect how you should interpret accumulator updates.
Mistake 3: Forgetting lazy evaluation
Creating a transformation does not necessarily execute it immediately.
An action is generally required to trigger execution.
Mistake 4: Collecting data unnecessarily
Don’t use:
df.collect()
just to calculate a metric that Spark can calculate in a distributed manner.
Mistake 5: Using accumulators for state management
Accumulators are not a general-purpose distributed state store.
22. Accumulator Interview Questions
Q1. What is an accumulator in Spark?
An accumulator is a variable that tasks can add to while the driver can read the accumulated value.
Q2. What is the primary use case?
Counters and diagnostic information.
Q3. Can executors read an accumulator?
Executors can update accumulators, but accumulator usage is primarily designed around updates from tasks and reading the final value on the driver.
Q4. Can you use an accumulator to calculate total revenue?
Technically you could increment one, but it is not the recommended approach for a business-critical aggregation.
Use Spark’s distributed aggregation functions instead.
Q5. What happens if a Spark task is retried?
You need to account for retries and recomputation when interpreting accumulator updates. This is why accumulators should not be relied upon for critical business correctness.
Q6. What is the difference between accumulator and broadcast variable?
Broadcast:
Driver → Executors
Accumulator:
Executors → Driver
Q7. Are accumulators immediately updated when you define a transformation?
No. Spark uses lazy evaluation, so execution generally occurs only when an action triggers the computation.
Q8. Can accumulators be used for debugging?
Yes. Tracking counters such as invalid records or skipped records is a common use case.
Q9. Should accumulators be used for production business metrics?
Generally, no. For authoritative business metrics, use explicit Spark/DataFrame/SQL computations and persist the results when needed.
23. Accumulators: The Mental Model
If you remember only one diagram from this tutorial, remember this:
DRIVER
│
│
Spark Job
│
┌───────────┼───────────┐
▼ ▼ ▼
Executor 1 Executor 2 Executor 3
│ │ │
+100 +50 +25
│ │ │
└───────────┼───────────┘
▼
ACCUMULATOR
│
▼
DRIVER
value = 175
The key idea:
Accumulators provide a controlled way for distributed tasks to contribute to an aggregated value that the driver can inspect.
24. Key Takeaways
Accumulators are a useful Spark concept, particularly for understanding distributed execution.
Remember:
- Accumulators are shared variables designed for aggregation.
- Executors can update them.
- The driver can read their value.
- They are commonly used for counters and diagnostics.
- They work within Spark’s distributed execution model.
- Lazy evaluation means transformations don’t immediately execute.
- Task retries and recomputation can complicate accumulator semantics.
- Don’t use accumulators as the source of truth for critical business calculations.
- Use DataFrame and SQL aggregations for business logic.
- Broadcast variables and accumulators solve opposite communication problems.
Continue Learning PySpark
You’ve now learned how Spark can collect diagnostic information from distributed tasks using accumulators.
The next step is to understand one of the most important performance optimizations for large Spark joins: Broadcast Joins.
Recommended next steps:
1. Broadcast Joins
Learn how Spark can distribute a small DataFrame across executors to avoid expensive shuffle operations.
2. Shuffle
Understand how Spark moves data between partitions during joins and aggregations.
3. Data Skew
Learn why a few heavily concentrated keys can make one Spark task dramatically slower than the others.
4. Salting
Learn how to distribute skewed keys across multiple partitions.
[View Complete Apache Spark Learning Path →]
You Might Need This Next
⚡ Debugging Spark jobs?
If you’re using accumulators to understand what’s happening inside a distributed job, the next thing to learn is Spark UI.
Spark UI helps you investigate:
→ Jobs
→ Stages
→ Tasks
→ Shuffle
→ Task duration
→ Executor metrics
→ SQL execution plans
Read next: Spark UI and Production Troubleshooting →
Next Step
You’ve learned how accumulators allow Spark tasks to contribute diagnostic information back to the driver.
But Spark performance optimization becomes particularly important when you’re joining a massive dataset with a relatively small dataset.
In the next tutorial, we’ll explore:
- What a Broadcast Join is
- BroadcastHashJoin
- How Spark decides whether to broadcast
- Broadcast join hints
- Broadcast thresholds
- Memory considerations
- Broadcast Join vs Shuffle Join
- Real-world PySpark examples
Next: Understand Broadcast Joins →
Further Reading
- Apache Spark — RDD Programming Guide — Official Spark documentation covering shared variables, accumulators, and distributed execution.
- Apache Spark — PySpark API Reference — Official PySpark API documentation.
- Apache Spark — Monitoring and Instrumentation — Official documentation for monitoring Spark applications and using the Spark UI.
- Apache Spark — Performance Tuning — Official Spark documentation covering SQL/DataFrame performance optimization.
- Databricks — Apache Spark — Databricks documentation for Spark workloads.
Related Tutorials
- ✓ What is Apache Spark?
- ✓ Why Spark is Faster than Hadoop
- ✓ Spark Architecture
- ✓ Driver vs Executor
- ✓ Cluster Managers
- → RDD vs DataFrame vs Dataset
- → SparkSession
- ✓ Reading Data
- ✓ Lazy Evaluation
- ✓ DAG
- ✓ Stages and Tasks
- ✓ Shuffle
- ✓ Narrow vs Wide Transformations
- → Writing Data
- → Data Transformations
- →Window Functions
- Aggregations
- UDFs
- Broadcast variables
1 thought on “Accumulators in Apache Spark: How They Work, When to Use Them, and Common Pitfalls”