Skip to content

Nine hundred ninety-nine tasks finished. Why is the last one still running?

The Batch Finished. What Did We Actually Produce? · Part 2 of 6 · 5 min read

Read the worked explanation ↓

Interview question. A Spark job launches 1,000 tasks. Almost all finish in ten minutes, but one continues for two hours. Your team proposes enabling speculative execution and doubling the cluster. Before agreeing, distinguish a slow machine from a partition containing unusually large work. Explain when speculation helps, when it wastes capacity, and how you would redesign a skewed calculation.

The job groups event records by customer. A few customers generate far more events than the rest. The output requires exact counts per customer, not a strict event-by-event processing order.

Think first

One reducer reads 180 GB, while others read 2–4 GB. A teammate proposes 500 additional workers. Diagnose and propose a safe alternative for an exact per-customer count.

Take a few minutes to form an approach, then compare it with the worked explanation.

Reveal the worked explanation

Two slow tasks can mean very different things

Imagine watching two execution timelines. In one, task 417 processes approximately the same number of records as every other task but crawls on a host with a failing disk. In the other, task 417 runs on a healthy host and has fifty times the input of the median task. Their symptoms are similar. Their fixes are not.

I would compare input records, input bytes, shuffle bytes read, spill volume, CPU time, garbage collection, and remote-fetch waiting time for the slow task against peers. Then I would compare the host's disk, CPU and network metrics. A bad host often shows abnormal processing rate for an otherwise ordinary partition. Skew shows abnormal work size, even if the worker is functioning perfectly.

Task duration by itself cannot distinguish them. Neither can overall cluster CPU utilization. A cluster can look idle because 999 tasks have finished and one partition still has most of the remaining work.

Side-by-side task charts compare equal-sized tasks with a slow worker and one severely skewed input partition.
Side-by-side task charts compare equal-sized tasks with a slow worker and one severely skewed input partition.

What speculative execution actually does

Speculation starts another attempt of a task that appears unusually slow. If the first attempt is stuck on a struggling machine, a copy running elsewhere may finish sooner. Spark exposes speculation settings that identify slow tasks relative to others and may relaunch them. These are configurable and do not mean the system always speculates by default. Spark configuration

Now consider a customer with 400 million rows assigned to one reduce partition. Launching an identical attempt elsewhere sends the same large workload to a second worker. The partition has not become smaller, and both attempts may compete for shuffle reads, storage bandwidth and memory. Speculation can make total resource consumption worse while the stage still waits for one full copy of that work to finish.

For our scenario, I would not start by toggling speculation. I would look at the per-task bytes and records, then identify heavy keys. Sampling can help, but sampling must be sufficient to detect rare very large groups. A high-cardinality aggregate with uniform keys may have a completely different explanation from a few huge customer groups.

Splitting a key without changing its answer

Exact counts are convenient here because partial counts can be combined. For the heavy customer, split its records across several salted subkeys such as (customer_17, bucket_0) through (customer_17, bucket_15). Compute 16 partial counts, then sum them in a second stage to get the customer's total.

The bucket must be derived from something that distributes records, not just from customer_17, or every event will still land in the same bucket. A hash of a stable event ID is one option. If duplicate events exist, the counting contract must determine whether to deduplicate before counting.

But I would not salt every customer automatically. It increases intermediate keys, shuffle work and aggregation stages. Reserve the extra partitioning for keys that actually need it, or use an adaptive mechanism that can split skewed partitions where it is supported. Spark's adaptive query execution can optimize particular skewed joins, but that does not imply it automatically parallelizes every arbitrary per-key aggregation. Spark SQL tuning

The same technique is not safe for every question. If each customer requires a strict sequence of stateful updates, processing pieces independently may violate the meaning of the computation. Even for numeric aggregation, floating-point regrouping can alter the last few bits of a result, so an exactness requirement needs a defined numeric model.

Why doubling the cluster might achieve nothing

Assume the job is blocked on one nonparallel reducer that needs 120 minutes on its current hardware. Doubling the number of idle executors does not make that reducer finish in 60 minutes. Its work must become divisible or the task must become faster.

There are legitimate reasons to add capacity. If many large partitions are waiting in the scheduler, extra slots can help. If the slow task suffers because its host is overloaded by other work, moving or isolating it can help. If the entire stage is throttled by shared storage, extra workers may instead increase pressure on that dependency.

I would test one targeted change and observe whether the tail task's processed records per second improves. A reduction in average task duration that leaves the critical tail unchanged is not a useful outcome for this incident.

One more production concern: speculative attempts and task retries can execute code more than once. A task that directly sends an email or updates an external database must not rely on the scheduler for exactly-once business effects. Batch computations should write into a controlled output commit path rather than allowing every attempt to publish irreversible side effects.

Once skew is under control, the next question is whether a join has to move both huge datasets across the cluster at all.

Your decision. The longest task has 45 times more records than the median, but its records-per-second rate is healthy. Would you enable speculation, add nodes, or change the key layout? Explain the first experiment.

Go deeper

Optional depth: speculation versus a heavy-key split

Speculation helps with variance in task execution at a given work size. A split changes the work assigned to the task. Those are orthogonal interventions. In joins, an adaptive engine may split skewed shuffle partitions and replicate the matching portion of the other side so results remain complete. In aggregation, partial-state merging is appropriate only if the aggregate supports it. A median, order-sensitive state machine or non-associative transformation requires more care than a sum.

Check your reasoning

  • Contrast input size skew with a slow processing rate on one host.
  • Explain when speculation replicates the heavy work and worsens pressure.
  • Use stable record-based salt buckets for combinable partial aggregation.
  • Distinguish associative results from order-sensitive or non-associative computations.

Common mistake: Speculation makes a skewed partition smaller.

One task processes 50 times more records but at normal per-record speed. Which diagnosis fits?

Read the answer explanation

Data skew that needs different work partitioning — The task is slow primarily because its assigned work is abnormally large.

Apply it elsewhere

A graph analytics job has one node with 100 million edges. How should its work be distributed?

Compare your approach

Ask whether the algorithm tolerates independent edge batches and an associative combination of results.

One more constraint

Would randomizing only the order of input records fix the issue if the partition key stays customer ID?

Quick revision

One slow task can be a weak machine or a much larger partition. I would compare bytes, records, processing rate, spill, and host metrics. Speculation may help a weak machine, but it duplicates the same work when a heavy key owns the partition. For associative aggregation, distribute the heavy key into partial groups and merge them. Verify that the calculation allows the split before applying it.

References