[SPARK-58935][CORE] Expose numDroppedExecutorManagementEvents metric - #58216
[SPARK-58935][CORE] Expose numDroppedExecutorManagementEvents metric#58216HwangDongJun wants to merge 1 commit into
Conversation
### What changes were proposed in this pull request? `LiveListenerBus` delivers the same logical event to several independent, separately-capacity-limited queues. `ExecutorAllocationManager`'s listener is registered on the `executorManagement` queue, while the listener that drives the Spark UI is registered on a completely separate `appStatus` queue. Each queue independently drops events once its bounded capacity is exceeded, and a dropped event is never redelivered or resynced. If a `SparkListenerStageSubmitted` event is dropped specifically from the `executorManagement` queue, `ExecutorAllocationManager` never learns about that stage's tasks, and its "executors needed" calculation permanently omits them for the lifetime of the stage -- even though the Spark UI/REST API (backed by the unaffected `appStatus` queue) continues to show the stage as `RUNNING` normally. There is currently no way to observe this happening: the only existing signal is a generic, easy-to-miss `WARN` log line shared by every queue, logged at most once per 60 seconds, with no indication of which downstream component is affected. This PR adds `LiveListenerBus.numDroppedExecutorManagementEvents`, which exposes the `executorManagement` queue's dropped-event counter, and a corresponding `ExecutorAllocationManager` delegate method. It registers a new `numDroppedExecutorManagementEvents` gauge on `ExecutorAllocationManagerSource`, matching the existing pattern used by that source's other gauges (e.g. `numberMaxNeededExecutors`), so operators can alert on it and correlate "dynamic allocation stopped requesting executors" with "the executorManagement queue actually dropped an event." A full self-healing fix (e.g., periodically reconciling `ExecutorAllocationManager`'s bookkeeping against the ground-truth stage/task state already tracked by `AppStatusStore`) is a larger, more invasive change that needs broader design discussion. This PR is intentionally scoped to making the problem observable, not to fixing the underlying event-drop behavior. ### Why are the changes needed? Dynamic allocation can silently stop requesting new executors for an application that, from the UI/REST API and logs, looks completely healthy and busy, with no error, warning, or other signal indicating which component was affected. This was observed in production on a long-running Spark Connect driver, where `ExecutorAllocationManager`'s JMX metrics showed `numberMaxNeededExecutors = 0` and `numberTargetExecutors = 0` with pending tasks on an active stage, while the Spark UI simultaneously reported the job as `RUNNING`. The only workaround was restarting the driver process. SPARK-32597 previously identified that event drops in the async listener bus can cause general inconsistent application state, and proposed a more invasive `VariableLinkedBlockingQueue` approach (closed unmerged in 2020). This PR documents a specific, reproducible manifestation of that general class of problem, with a much narrower first fix. SPARK-58446 reports a similarly-surfacing symptom (dynamic allocation stuck at zero needed/target executors), but from a distinct mechanism: a late `TaskStart`/`SpeculativeTaskSubmitted` event arriving after `onStageCompleted` corrupts the pending-task count. That fix does not touch `onStageSubmitted` and would not prevent or fix the issue described here, where `stageAttemptToNumTasks` is never populated for the affected stage attempt in the first place because the `SparkListenerStageSubmitted` event never reaches the listener. ### Does this PR introduce _any_ user-facing change? Yes. A new `numDroppedExecutorManagementEvents` gauge metric is exposed under the `ExecutorAllocationManagerSource` metrics namespace. This PR does not change any existing behavior. ### How was this patch tested? * Added an `ExecutorAllocationManagerSuite` regression test that deterministically forces a `SparkListenerStageSubmitted` event to be dropped from the `executorManagement` queue (by setting its capacity to 1 and occupying its single dispatch thread with a blocking listener), and asserts that `numDroppedExecutorManagementEvents` reflects the drop and that `maxNumExecutorsNeededPerResourceProfile` is left at 0 for that stage, even though it has pending tasks. * Added a `SparkListenerSuite` test verifying, in both directions, that `numDroppedExecutorManagementEvents` tracks drops on the `executorManagement` queue only, and is unaffected by drops on the shared queue. * Ran the full `ExecutorAllocationManagerSuite` (38/38), `SparkListenerSuite` (24/24), and `ExecutorMonitorSuite` (16/16, regression check): all passed. * `scalastyle`: no violations. ### Was this patch authored or co-authored using generative AI tooling? Generated-by: Claude Sonnet 5
|
Thank you @HwangDongJun! |
|
Thanks for the detailed writeup and for including a repro test. The failure mode you describe -- the That said, I don't think we need a new metric for it, because the per-queue dropped-event counter is already exported.
and that source ( So It is also strictly less available than the existing one: whereas the What I believe is the actual gap here is documentation. In https://github.com/apache/spark/blob/master/docs/monitoring.md?plain=1#L1327-L1338 If an operator could not find this signal, that omission seems the likely reason. Would you consider re-scoping this PR to fill in those missing lines? That makes exactly the signal you needed discoverable, with no new API surface and no duplicated metric. A few other notes, in case parts of the change are kept:
|
dongjoon-hyun
left a comment
There was a problem hiding this comment.
Please consider the above comment and double-check, @HwangDongJun .
What changes were proposed in this pull request?
LiveListenerBusdelivers the same logical event to several independent, separately-capacity-limited queues.ExecutorAllocationManager's listener is registered on theexecutorManagementqueue, while the listener that drives the Spark UI is registered on a completely separateappStatusqueue. Each queue independently drops events once its bounded capacity is exceeded, and a dropped event is never redelivered or resynced.If a
SparkListenerStageSubmittedevent is dropped specifically from theexecutorManagementqueue,ExecutorAllocationManagernever learns about that stage's tasks, and its "executors needed" calculation permanently omits them for the lifetime of the stage -- even though the Spark UI/REST API (backed by the unaffectedappStatusqueue) continues to show the stage asRUNNINGnormally. There is currently no way to observe this happening: the only existing signal is a generic, easy-to-missWARNlog line shared by every queue, logged at most once per 60 seconds, with no indication of which downstream component is affected.This PR adds
LiveListenerBus.numDroppedExecutorManagementEvents, which exposes theexecutorManagementqueue's dropped-event counter, and a correspondingExecutorAllocationManagerdelegate method. It registers a newnumDroppedExecutorManagementEventsgauge onExecutorAllocationManagerSource, matching the existing pattern used by that source's other gauges (e.g.numberMaxNeededExecutors), so operators can alert on it and correlate "dynamic allocation stopped requesting executors" with "the executorManagement queue actually dropped an event."A full self-healing fix (e.g., periodically reconciling
ExecutorAllocationManager's bookkeeping against the ground-truth stage/task state already tracked byAppStatusStore) is a larger, more invasive change that needs broader design discussion. This PR is intentionally scoped to making the problem observable, not to fixing the underlying event-drop behavior.Why are the changes needed?
Dynamic allocation can silently stop requesting new executors for an application that, from the UI/REST API and logs, looks completely healthy and busy, with no error, warning, or other signal indicating which component was affected. This was observed in production on a long-running Spark Connect driver, where
ExecutorAllocationManager's JMX metrics showednumberMaxNeededExecutors = 0andnumberTargetExecutors = 0with pending tasks on an active stage, while the Spark UI simultaneously reported the job asRUNNING. The only workaround was restarting the driver process.SPARK-32597 previously identified that event drops in the async listener bus can cause general inconsistent application state, and proposed a more invasive
VariableLinkedBlockingQueueapproach (closed unmerged in 2020). This PR documents a specific, reproducible manifestation of that general class of problem, with a much narrower first fix.SPARK-58446 reports a similarly-surfacing symptom (dynamic allocation stuck at zero needed/target executors), but from a distinct mechanism: a late
TaskStart/SpeculativeTaskSubmittedevent arriving afteronStageCompletedcorrupts the pending-task count. That fix does not touchonStageSubmittedand would not prevent or fix the issue described here, wherestageAttemptToNumTasksis never populated for the affected stage attempt in the first place because theSparkListenerStageSubmittedevent never reaches the listener.Does this PR introduce any user-facing change?
Yes. A new
numDroppedExecutorManagementEventsgauge metric is exposed under theExecutorAllocationManagerSourcemetrics namespace. This PR does not change any existing behavior.How was this patch tested?
ExecutorAllocationManagerSuiteregression test that deterministically forces aSparkListenerStageSubmittedevent to be dropped from theexecutorManagementqueue (by setting its capacity to 1 and occupying its single dispatch thread with a blocking listener), and asserts thatnumDroppedExecutorManagementEventsreflects the drop and thatmaxNumExecutorsNeededPerResourceProfileis left at 0 for that stage, even though it has pending tasks.SparkListenerSuitetest verifying, in both directions, thatnumDroppedExecutorManagementEventstracks drops on theexecutorManagementqueue only, and is unaffected by drops on the shared queue.ExecutorAllocationManagerSuite(38/38),SparkListenerSuite(24/24), andExecutorMonitorSuite(16/16, regression check): all passed.scalastyle: no violations.Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Sonnet 5