This is an automated email from the ASF dual-hosted git repository.

shauryachats pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new 26bf075d16c Skip missing consuming segment check for paused realtime 
tables (#19310)
26bf075d16c is described below

commit 26bf075d16cfc6d80d5682589109f873a9c6dd96
Author: rohit <[email protected]>
AuthorDate: Sat Sep 12 04:00:33 2026 +0530

    Skip missing consuming segment check for paused realtime tables (#19310)
    
    SegmentStatusChecker invoked MissingConsumingSegmentFinder for every
    enabled REALTIME table without checking whether ingestion was paused.
    Pausing deliberately leaves a partition with no CONSUMING segment once
    the current one commits, so the finder read that as a missing consumer
    and the missingConsumingSegment* gauges reported a growing false
    positive for as long as the pause lasted.
    
    Gate the finder on the table pause state already computed in
    updateSegmentMetrics. Because these gauges are last-write-wins with no
    "unset" value, the paused branch actively resets them via a new
    MissingConsumingSegmentFinder.resetMetrics, keeping ownership of the
    gauge names with the class that emits them.
---
 .../controller/helix/SegmentStatusChecker.java     | 16 +++-
 .../realtime/MissingConsumingSegmentFinder.java    | 12 +++
 .../controller/helix/SegmentStatusCheckerTest.java | 86 ++++++++++++++++++++++
 3 files changed, 110 insertions(+), 4 deletions(-)

diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/SegmentStatusChecker.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/SegmentStatusChecker.java
index cb27b48a9d7..423a3891bc6 100644
--- 
a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/SegmentStatusChecker.java
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/SegmentStatusChecker.java
@@ -300,7 +300,8 @@ public class SegmentStatusChecker extends 
ControllerPeriodicTask<SegmentStatusCh
       return false;
     }
 
-    if (PinotLLCRealtimeSegmentManager.isTablePaused(idealState)) {
+    boolean tablePaused = 
PinotLLCRealtimeSegmentManager.isTablePaused(idealState);
+    if (tablePaused) {
       context._pausedTables.add(tableNameWithType);
     }
 
@@ -585,9 +586,16 @@ public class SegmentStatusChecker extends 
ControllerPeriodicTask<SegmentStatusCh
         numInvalidEndTime);
 
     if (tableType == TableType.REALTIME && tableConfig != null) {
-      List<StreamConfig> streamConfigs = 
IngestionConfigUtils.getStreamConfigs(tableConfig);
-      new MissingConsumingSegmentFinder(tableNameWithType, propertyStore, 
_controllerMetrics,
-          streamConfigs, idealState).findAndEmitMetrics(idealState);
+      if (tablePaused) {
+        // Ingestion is intentionally paused, so 
PinotLLCRealtimeSegmentManager deliberately does not create a new
+        // CONSUMING segment after the current one commits. 
MissingConsumingSegmentFinder would otherwise read that as
+        // a missing segment and alert for as long as the pause lasts.
+        MissingConsumingSegmentFinder.resetMetrics(tableNameWithType, 
_controllerMetrics);
+      } else {
+        List<StreamConfig> streamConfigs = 
IngestionConfigUtils.getStreamConfigs(tableConfig);
+        new MissingConsumingSegmentFinder(tableNameWithType, propertyStore, 
_controllerMetrics,
+            streamConfigs, idealState).findAndEmitMetrics(idealState);
+      }
     }
     return true;
   }
diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/MissingConsumingSegmentFinder.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/MissingConsumingSegmentFinder.java
index 56d47cba7bc..13791ecdfc7 100644
--- 
a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/MissingConsumingSegmentFinder.java
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/MissingConsumingSegmentFinder.java
@@ -107,6 +107,18 @@ public class MissingConsumingSegmentFinder {
     _streamPartitionMsgOffsetFactory = streamPartitionMsgOffsetFactory;
   }
 
+  /// Zeroes out every gauge that [#findAndEmitMetrics] emits, for callers 
that intentionally skip the search (e.g. a
+  /// table whose ingestion is paused, where having no CONSUMING segment is 
expected rather than a defect). These
+  /// gauges are last-write-wins with no "unset" value, so a skipped table 
would otherwise keep reporting whatever was
+  /// last written before the skip began. Kept here so the class that owns 
these gauge names also owns their reset.
+  public static void resetMetrics(String realtimeTableName, ControllerMetrics 
controllerMetrics) {
+    controllerMetrics.setValueOfTableGauge(realtimeTableName, 
ControllerGauge.MISSING_CONSUMING_SEGMENT_TOTAL_COUNT, 0);
+    controllerMetrics.setValueOfTableGauge(realtimeTableName,
+        ControllerGauge.MISSING_CONSUMING_SEGMENT_NEW_PARTITION_COUNT, 0);
+    controllerMetrics.setValueOfTableGauge(realtimeTableName,
+        ControllerGauge.MISSING_CONSUMING_SEGMENT_MAX_DURATION_MINUTES, 0);
+  }
+
   public void findAndEmitMetrics(IdealState idealState) {
     MissingSegmentInfo info = 
findMissingSegments(idealState.getRecord().getMapFields(), Instant.now());
     _controllerMetrics.setValueOfTableGauge(_realtimeTableName, 
ControllerGauge.MISSING_CONSUMING_SEGMENT_TOTAL_COUNT,
diff --git 
a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/SegmentStatusCheckerTest.java
 
b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/SegmentStatusCheckerTest.java
index 02e333eb52c..c9fb1f6c0a0 100644
--- 
a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/SegmentStatusCheckerTest.java
+++ 
b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/SegmentStatusCheckerTest.java
@@ -48,7 +48,9 @@ import org.apache.pinot.controller.LeadControllerManager;
 import org.apache.pinot.controller.api.resources.SegmentStatusInfo;
 import org.apache.pinot.controller.api.resources.TableViews;
 import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
+import 
org.apache.pinot.controller.helix.core.realtime.PinotLLCRealtimeSegmentManager;
 import org.apache.pinot.controller.util.TableSizeReader;
+import org.apache.pinot.spi.config.table.PauseState;
 import org.apache.pinot.spi.config.table.TableConfig;
 import org.apache.pinot.spi.config.table.TableType;
 import org.apache.pinot.spi.config.table.TierConfig;
@@ -319,6 +321,90 @@ public class SegmentStatusCheckerTest {
         ControllerGauge.MISSING_CONSUMING_SEGMENT_TOTAL_COUNT), 2);
   }
 
+  /// When a table is paused via the pause/resume ingestion API, 
PinotLLCRealtimeSegmentManager deliberately does not
+  /// create a new CONSUMING segment after the current one commits. Without 
the pause gate,
+  /// MissingConsumingSegmentFinder would see the completed segments with no 
CONSUMING replacement and flag them as
+  /// missing for as long as the pause lasts.
+  ///
+  /// The fixture matches [#realtimeBasicTest], which reports a
+  /// [ControllerGauge#MISSING_CONSUMING_SEGMENT_TOTAL_COUNT] of 2. The 
checker is run twice against the same metrics
+  /// instance — once unpaused to establish that non-zero reading, then again 
after pausing — so this covers the
+  /// actual production scenario: these gauges are last-write-wins, so pausing 
must actively reset a previously
+  /// reported value rather than merely stop updating it.
+  @Test
+  public void realtimePausedTableHasNoMissingConsumingSegmentAlert() {
+    TableConfig tableConfig =
+        new 
TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setTimeColumnName("timeColumn")
+            .setNumReplicas(3).setStreamConfigs(getStreamConfigMap()).build();
+
+    String seg1 = new LLCSegmentName(RAW_TABLE_NAME, 1, 0, 
System.currentTimeMillis()).getSegmentName();
+    String seg2 = new LLCSegmentName(RAW_TABLE_NAME, 1, 1, 
System.currentTimeMillis()).getSegmentName();
+    String seg3 = new LLCSegmentName(RAW_TABLE_NAME, 2, 1, 
System.currentTimeMillis()).getSegmentName();
+    IdealState idealState = new IdealState(REALTIME_TABLE_NAME);
+    idealState.setPartitionState(seg1, "pinot1", "ONLINE");
+    idealState.setPartitionState(seg1, "pinot2", "ONLINE");
+    idealState.setPartitionState(seg1, "pinot3", "ONLINE");
+
+    idealState.setPartitionState(seg2, "pinot1", "ONLINE");
+    idealState.setPartitionState(seg2, "pinot2", "ONLINE");
+    idealState.setPartitionState(seg2, "pinot3", "ONLINE");
+
+    idealState.setPartitionState(seg3, "pinot1", "CONSUMING");
+    idealState.setPartitionState(seg3, "pinot2", "CONSUMING");
+    idealState.setPartitionState(seg3, "pinot3", "OFFLINE");
+    idealState.setReplicas("3");
+    idealState.setRebalanceMode(IdealState.RebalanceMode.CUSTOMIZED);
+
+    ExternalView externalView = new ExternalView(REALTIME_TABLE_NAME);
+    externalView.setState(seg1, "pinot1", "ONLINE");
+    externalView.setState(seg1, "pinot2", "ONLINE");
+    externalView.setState(seg1, "pinot3", "ONLINE");
+
+    externalView.setState(seg2, "pinot1", "CONSUMING");
+    externalView.setState(seg2, "pinot2", "ONLINE");
+    externalView.setState(seg2, "pinot3", "CONSUMING");
+
+    externalView.setState(seg3, "pinot1", "CONSUMING");
+    externalView.setState(seg3, "pinot2", "CONSUMING");
+    externalView.setState(seg3, "pinot3", "OFFLINE");
+
+    PinotHelixResourceManager resourceManager = 
mock(PinotHelixResourceManager.class);
+    
when(resourceManager.getHelixInstanceConfig(any())).thenReturn(newQuerableInstanceConfig("any"));
+    
when(resourceManager.getTableConfig(REALTIME_TABLE_NAME)).thenReturn(tableConfig);
+    
when(resourceManager.getAllTables()).thenReturn(List.of(REALTIME_TABLE_NAME));
+    
when(resourceManager.getTableIdealState(REALTIME_TABLE_NAME)).thenReturn(idealState);
+    
when(resourceManager.getTableExternalView(REALTIME_TABLE_NAME)).thenReturn(externalView);
+    SegmentZKMetadata committedSegmentZKMetadata = 
mockCommittedSegmentZKMetadata();
+    SegmentZKMetadata consumingSegmentZKMetadata = 
mockConsumingSegmentZKMetadata(11111L);
+    mockSegmentsZKMetadata(resourceManager, REALTIME_TABLE_NAME,
+        Map.of(seg1, committedSegmentZKMetadata, seg2, 
committedSegmentZKMetadata, seg3, consumingSegmentZKMetadata));
+
+    // Stubbed exactly as in realtimeBasicTest so that 
MissingConsumingSegmentFinder computes a non-zero count.
+    ZkHelixPropertyStore<ZNRecord> propertyStore = 
mock(ZkHelixPropertyStore.class);
+    when(resourceManager.getPropertyStore()).thenReturn(propertyStore);
+    ZNRecord znRecord = new ZNRecord("0");
+    znRecord.setSimpleField(CommonConstants.Segment.Realtime.END_OFFSET, 
"10000");
+    when(propertyStore.get(anyString(), any(), anyInt())).thenReturn(znRecord);
+
+    // Not yet paused: the finder runs and reports the missing consuming 
segments.
+    runSegmentStatusChecker(resourceManager, 0);
+    assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, 
REALTIME_TABLE_NAME,
+        ControllerGauge.MISSING_CONSUMING_SEGMENT_TOTAL_COUNT), 2);
+
+    // Mirrors how PinotLLCRealtimeSegmentManager#updatePauseStateInIdealState 
records an administrative pause, and how
+    // #isTablePaused reads it back off the ideal state record.
+    
idealState.getRecord().setSimpleField(PinotLLCRealtimeSegmentManager.PAUSE_STATE,
+        new PauseState(true, PauseState.ReasonCode.ADMINISTRATIVE, "paused for 
test", "0", null).toJsonString());
+
+    runSegmentStatusChecker(resourceManager, 0);
+    assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, 
REALTIME_TABLE_NAME,
+        ControllerGauge.MISSING_CONSUMING_SEGMENT_TOTAL_COUNT), 0);
+    assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, 
REALTIME_TABLE_NAME,
+        ControllerGauge.MISSING_CONSUMING_SEGMENT_NEW_PARTITION_COUNT), 0);
+    assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, 
REALTIME_TABLE_NAME,
+        ControllerGauge.MISSING_CONSUMING_SEGMENT_MAX_DURATION_MINUTES), 0);
+  }
+
   @Test
   public void realtimeMutableSegmentHasLessReplicaTest() {
     TableConfig tableConfig =


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to