Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1342,6 +1342,21 @@ private void enqueueEvent(ClusterEventBlockingQueue queue, ClusterEvent event) {
return;
}
queue.put(event);
if (queue == _eventQueue) {
updateControllerEventQueueSizeGauge();
}
}

/**
* Publish the current DEFAULT cluster-event pipeline backlog to the per-cluster monitor. Invoked
* on both the enqueue side (ZK-callback / periodic-rebalance threads) and the dequeue side (the
* pipeline thread) so the gauge climbs when events pile up faster than they are drained, which
* surfaces a controller that still holds leadership but has stopped processing ("zombie leader").
*/
private void updateControllerEventQueueSizeGauge() {
if (_isMonitoring && _clusterStatusMonitor != null && _eventQueue != null) {
_clusterStatusMonitor.setControllerEventQueueSizeGauge(_eventQueue.size());
}
}

@Override
Expand Down Expand Up @@ -1579,6 +1594,9 @@ public void run() {
while (!isInterrupted()) {
try {
ClusterEvent newClusterEvent = _eventBlockingQueue.take();
if (_eventBlockingQueue == _eventQueue) {
updateControllerEventQueueSizeGauge();
}
String threadName = String.format(
"HelixController-pipeline-%s-(%s)", _processorName, newClusterEvent.getEventId());
this.setName(threadName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ public class ClusterStatusMonitor implements ClusterStatusMonitorMBean {
private AtomicLong _rebalanceFailureCount = new AtomicLong(0L);
private AtomicLong _continuousResourceRebalanceFailureCount = new AtomicLong(0L);
private AtomicLong _continuousTaskRebalanceFailureCount = new AtomicLong(0L);
// DEFAULT controller cluster-event pipeline backlog. Near 0 on a healthy controller (the queue
// dedups by event type); climbs when the controller still holds leadership but stops draining
// events, surfacing the "zombie leader" failure mode.
private AtomicLong _controllerEventQueueSizeGauge = new AtomicLong(0L);

// WAGED per-FailureCategory counters. Populated in the constructor with a zero AtomicLong per
// enum value so reads on never-incremented categories return 0 instead of NPE.
Expand Down Expand Up @@ -1467,6 +1471,16 @@ public void reportContinuousTaskRebalanceFailureCount(long newValue) {
_continuousTaskRebalanceFailureCount.set(newValue);
}

/**
* Surface the DEFAULT controller cluster-event pipeline backlog as a JMX gauge. A healthy
* controller drains events quickly so this stays near 0 (the queue dedups by event type); a
* wedged controller that still holds leadership but stops processing lets it climb, making the
* "zombie leader" failure mode detectable.
*/
public void setControllerEventQueueSizeGauge(long size) {
_controllerEventQueueSizeGauge.set(size);
}

@Override
public long getRebalanceFailureCounter() {
return _rebalanceFailureCount.get();
Expand All @@ -1482,6 +1496,11 @@ public long getContinuousTaskRebalanceFailureCount() {
return _continuousTaskRebalanceFailureCount.get();
}

@Override
public long getControllerEventQueueSizeGauge() {
return _controllerEventQueueSizeGauge.get();
}

@Override
public long getWagedCustomerActionableFailureCounter() {
return _wagedCustomerActionableFailureCount.get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ public interface ClusterStatusMonitorMBean extends SensorNameProvider {
*/
long getContinuousTaskRebalanceFailureCount();

/**
* Backlog of the DEFAULT controller cluster-event pipeline (events enqueued but not yet
* processed). Stays near 0 on a healthy controller because the pipeline drains quickly and the
* queue dedups by event type; climbs when the controller holds leadership but has stopped
* processing events ("zombie leader"). Alert on a sustained average above ~0.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should combine this gauge with the progress signal Helix already has, ClusterEventStatus...TotalProcessed.EventCounter

The disambiguator is to combine this gauge with the progress signal Helix already has, ClusterEventStatus...TotalProcessed.EventCounter:

  • Idle + healthy: queue depth 0, counter flat.
  • Busy + healthy: queue depth occasionally > 0, counter advancing.
  • Wedged (zombie leader): queue depth sustained > 0, counter NOT advancing (and InQueue wait-time climbing).

So the robust alert is the conjunction: ControllerEventQueueSizeGauge sustained > 0 AND TotalProcessed.EventCounter rate ~= 0.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that will be done on alerting end. I've updated the java doc

* @return The current DEFAULT controller event queue size.
*/
long getControllerEventQueueSizeGauge();

// ---- WAGED failure-category counters (mirror of WagedRebalancerMetricCollector) ----
// Each WAGED HelixRebalanceException increments exactly one of these. The pair
// {WagedCustomerActionableFailureCounter, WagedInternalFailureCounter} is the recommended
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,24 @@ public void testWagedFallbackInUseGaugeReflectsLatestSetter() {
Assert.assertEquals(monitor.getWagedFallbackInUseGauge(), 0L);
}

@Test

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add tests that reflect this PR's behaviour like:

  • that enqueueing to the DEFAULT _eventQueue makes the gauge climb, and dequeueing brings it down
  • that only the DEFAULT queue updates it and the TASK queue (_taskEventQueue) does not (the queue == _eventQueue / _eventBlockingQueue == _eventQueue guards — the easiest thing to get wrong here, and untestable by the current tests)
  • that the value resets to 0 on leadership change (comment 2 — and a test here would have caught that the reset is missing)
  • nqueue several distinct event types without draining, assert the gauge reflects the backlog depth.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added for some which looked related. The rest (gauge climbs on enqueue / drops on dequeue, DEFAULT-vs-TASK queue discrimination, backlog depth across distinct event types) touch the GenericHelixController enqueue/dequeue wiring, so I'll cover those as a follow-up integration test.

public void testControllerEventQueueSizeGaugeStartsAtZero() {
ClusterStatusMonitor monitor = new ClusterStatusMonitor("TestControllerEventQueueGaugeCluster");
Assert.assertEquals(monitor.getControllerEventQueueSizeGauge(), 0L);
}

@Test
public void testControllerEventQueueSizeGaugeReflectsLatestSetter() {
ClusterStatusMonitor monitor =
new ClusterStatusMonitor("TestControllerEventQueueGaugeSetterCluster");
Assert.assertEquals(monitor.getControllerEventQueueSizeGauge(), 0L);
monitor.setControllerEventQueueSizeGauge(7L);
Assert.assertEquals(monitor.getControllerEventQueueSizeGauge(), 7L);
// Gauge is reversible: draining the pipeline takes it back down to 0.
monitor.setControllerEventQueueSizeGauge(0L);
Assert.assertEquals(monitor.getControllerEventQueueSizeGauge(), 0L);
}

@Test
public void testWagedHardConstraintCountersStartAtZero() {
ClusterStatusMonitor monitor = new ClusterStatusMonitor("TestWagedHardConstraintCluster");
Expand Down
Loading