Executor

Executor is a process that is used for executing tasks.

Executor typically runs for the entire lifetime of a Spark application which is called static allocation of executors (but you could also opt in for dynamic allocation).

Executors are managed by executor backends.

HeartbeatReceiver Heartbeat
Figure 1. HeartbeatReceiver’s Heartbeat Message Handler

Executors provide in-memory storage for RDDs that are cached in Spark applications (via BlockManager).

When started, an executor first registers itself with the driver that establishes a communication channel directly to the driver to accept tasks for execution.

executor taskrunner executorbackend
Figure 2. Launching tasks on executor using TaskRunners

Executor offers are described by executor id and the host on which an executor runs (see Resource Offers in this document).

Executors can run multiple tasks over its lifetime, both in parallel and sequentially. They track running tasks (by their task ids in runningTasks internal registry). Consult Launching Tasks section.

Executors send metrics (and heartbeats) using the internal heartbeater - Heartbeat Sender Thread.

It is recommended to have as many executors as data nodes and as many cores as you can get from the cluster.

Executors are described by their id, hostname, environment (as SparkEnv), and classpath (and, less importantly, and more for internal optimization, whether they run in local or cluster mode).

Creating Instance

Executor takes the following to be created:

When created, Executor prints out the following INFO messages to the logs:

Starting executor ID [executorId] on host [executorHostname]

(only for non-local modes) Executor sets SparkUncaughtExceptionHandler as the default handler invoked when a thread abruptly terminates due to an uncaught exception.

(only for non-local modes) Executor requests the BlockManager to initialize (with the Spark application id of the SparkConf).

(only for non-local modes) Executor requests the MetricsSystem to register the ExecutorSource and shuffleMetricsSource of the BlockManager.

Executor uses SparkEnv to access the MetricsSystem and BlockManager.

Executor creates a task class loader (optionally with REPL support) and requests the system Serializer to use as the default classloader (for deserializing tasks).

Executor is created when:

isLocal Flag

Executor is given a isLocal flag when created. This is how the executor knows whether it runs in local or cluster mode. It is disabled by default.

The flag is turned on for Spark local (via LocalEndpoint).

User-Defined Jars

Executor is given user-defined jars when created. There are no jars defined by default.

The jars are specified using spark.executor.extraClassPath configuration property (via --user-class-path command-line option of CoarseGrainedExecutorBackend).

Running Tasks

Executor tracks running tasks in a registry of TaskRunners per task ID.

HeartbeatReceiver RPC Endpoint Reference

updateDependencies Method

updateDependencies(
  newFiles: Map[String, Long],
  newJars: Map[String, Long]): Unit

updateDependencies…​FIXME

updateDependencies is used when TaskRunner is requested to start (and run a task).

Launching Task

launchTask(
  context: ExecutorBackend,
  taskDescription: TaskDescription): Unit

launchTask simply creates a TaskRunner (with the given ExecutorBackend and the TaskDescription) and adds it to the runningTasks internal registry.

In the end, launchTask requests the "Executor task launch worker" thread pool to execute the TaskRunner (sometime in the future).

executor taskrunner executorbackend
Figure 3. Launching tasks on executor using TaskRunners

launchTask is used when:

Heartbeat Sender Thread

heartbeater is a daemon ScheduledThreadPoolExecutor with a single thread.

The name of the thread pool is driver-heartbeater.

Coarse-Grained Executors

Coarse-grained executors are executors that use CoarseGrainedExecutorBackend for task scheduling.

Resource Offers

Read resourceOffers in TaskSchedulerImpl and resourceOffer in TaskSetManager.

Executor task launch worker Thread Pool

Executor uses threadPool daemon cached thread pool with the name Executor task launch worker-[ID] (with ID being the task id) for launching tasks.

threadPool is created when Executor is created and shut down when it stops.

Executor Memory

You can control the amount of memory per executor using spark.executor.memory configuration property. It sets the available memory equally for all executors per application.

The amount of memory per executor is looked up when SparkContext is created.

You can change the assigned memory per executor per node in standalone cluster using SPARK_EXECUTOR_MEMORY environment variable.

You can find the value displayed as Memory per Node in web UI for standalone Master (as depicted in the figure below).

spark standalone webui memory per node
Figure 4. Memory per Node in Spark Standalone’s web UI

The above figure shows the result of running Spark shell with the amount of memory per executor defined explicitly (on command line), i.e.

./bin/spark-shell --master spark://localhost:7077 -c spark.executor.memory=2g

Metrics

Every executor registers its own ExecutorSource to report metrics.

Stopping Executor

stop(): Unit

stop requests MetricsSystem for a report.

stop shuts driver-heartbeater thread down (and waits at most 10 seconds).

stop is used when CoarseGrainedExecutorBackend and LocalEndpoint are requested to stop their managed executors.

computeTotalGcTime Method

computeTotalGcTime(): Long

computeTotalGcTime…​FIXME

computeTotalGcTime is used when:

createClassLoader Method

createClassLoader(): MutableURLClassLoader

createClassLoader…​FIXME

createClassLoader is used when…​FIXME

addReplClassLoaderIfNeeded Method

addReplClassLoaderIfNeeded(
  parent: ClassLoader): ClassLoader

addReplClassLoaderIfNeeded…​FIXME

addReplClassLoaderIfNeeded is used when…​FIXME

Heartbeating With Partial Metrics For Active Tasks To Driver

reportHeartBeat(): Unit

reportHeartBeat collects TaskRunners for currently running tasks (aka active tasks) with their tasks deserialized (i.e. either ready for execution or already started).

TaskRunner has task deserialized when it runs the task.

For every running task, reportHeartBeat takes its TaskMetrics and:

reportHeartBeat then records the latest values of internal and external accumulators for every task.

Internal accumulators are a task’s metrics while external accumulators are a Spark application’s accumulators that a user has created.

reportHeartBeat sends a blocking Heartbeat message to HeartbeatReceiver endpoint (running on the driver). reportHeartBeat uses the value of spark.executor.heartbeatInterval configuration property for the RPC timeout.

A Heartbeat message contains the executor identifier, the accumulator updates, and the identifier of the BlockManager.

If the response (from HeartbeatReceiver endpoint) is to re-register the BlockManager, you should see the following INFO message in the logs and reportHeartBeat requests the BlockManager to re-register (which will register the blocks the BlockManager manages with the driver).

Told to re-register on heartbeat

HeartbeatResponse requests the BlockManager to re-register when either TaskScheduler or HeartbeatReceiver know nothing about the executor.

When posting the Heartbeat was successful, reportHeartBeat resets heartbeatFailures internal counter.

In case of a non-fatal exception, you should see the following WARN message in the logs (followed by the stack trace).

Issue communicating with driver in heartbeater

Every failure reportHeartBeat increments heartbeat failures up to spark.executor.heartbeat.maxFailures configuration property. When the heartbeat failures reaches the maximum, you should see the following ERROR message in the logs and the executor terminates with the error code: 56.

Exit as unable to send heartbeats to driver more than [HEARTBEAT_MAX_FAILURES] times

reportHeartBeat is used when Executor is requested to schedule reporting heartbeat and partial metrics for active tasks to the driver (that happens every spark.executor.heartbeatInterval).

Sending Heartbeats and Active Tasks Metrics

Executors keep sending metrics for active tasks to the driver every spark.executor.heartbeatInterval (defaults to 10s with some random initial delay so the heartbeats from different executors do not pile up on the driver).

executor heartbeatReceiver endpoint
Figure 5. Executors use HeartbeatReceiver endpoint to report task metrics

An executor sends heartbeats using the internal heartbeater — Heartbeat Sender Thread.

HeartbeatReceiver Heartbeat
Figure 6. HeartbeatReceiver’s Heartbeat Message Handler

For each task in TaskRunner (in runningTasks internal registry), the task’s metrics are computed (i.e. mergeShuffleReadMetrics and setJvmGCTime) that become part of the heartbeat (with accumulators).

Executors track the TaskRunner that run tasks. A task might not be assigned to a TaskRunner yet when the executor sends a heartbeat.

A blocking Heartbeat message that holds the executor id, all accumulator updates (per task id), and BlockManagerId is sent to HeartbeatReceiver RPC endpoint (with spark.executor.heartbeatInterval timeout).

If the response requests to reregister BlockManager, you should see the following INFO message in the logs:

Told to re-register on heartbeat

BlockManager is requested to reregister.

The internal heartbeatFailures counter is reset (i.e. becomes 0).

If there are any issues with communicating with the driver, you should see the following WARN message in the logs:

Issue communicating with driver in heartbeater

The internal heartbeatFailures is incremented and checked to be less than the acceptable number of failures (i.e. spark.executor.heartbeat.maxFailures Spark property). If the number is greater, the following ERROR is printed out to the logs:

Exit as unable to send heartbeats to driver more than [HEARTBEAT_MAX_FAILURES] times

The executor exits (using System.exit and exit code 56).

Logging

Enable ALL logging level for org.apache.spark.executor.Executor logger to see what happens inside.

Add the following line to conf/log4j.properties:

log4j.logger.org.apache.spark.executor.Executor=ALL

Refer to Logging.

Internal Properties

ExecutorSource

heartbeatFailures

maxDirectResultSize

maxResultSize

Used exclusively when TaskRunner is requested to run (and creates a serialized ByteBuffer result that is a IndirectTaskResult)