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

tarun11Mavani 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 684b7a4aa5f Add metric for local realtime segment builds with CRC 
mismatch (#19450)
684b7a4aa5f is described below

commit 684b7a4aa5f66173ffdfac058713e75adaec123f
Author: Prabhat Singh <[email protected]>
AuthorDate: Thu Nov 5 17:52:05 2026 +0530

    Add metric for local realtime segment builds with CRC mismatch (#19450)
    
    * Add metric for CRC-driven segment replacements
    
    Expose table-level observability for replicas that replace a segment after 
a confirmed CRC mismatch. Count only successful replacements so failed 
downloads and CRC read errors do not inflate the signal.
    
    Co-authored-by: Cursor <[email protected]>
    
    # Conflicts:
    #       
pinot-core/src/test/java/org/apache/pinot/core/data/manager/BaseTableDataManagerTest.java
    #       
pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/RealtimeSegmentDataManagerTest.java
    
    * Count CRC-driven replacements on the reloadSegment path
    
    reloadSegment can download and replace a segment after a CRC mismatch
    without going through replaceSegmentIfCrcMismatch, so the metric was
    missing those successful replacements. Same-CRC force downloads and
    failed reloads remain excluded.
    
    Co-authored-by: Cursor <[email protected]>
    
    # Conflicts:
    #       
pinot-core/src/main/java/org/apache/pinot/core/data/manager/BaseTableDataManager.java
    
    * Count confirmed CRC mismatches on locally built realtime segments
    
    Rename the meter to SEGMENT_BUILD_WITH_CRC_MISMATCH and emit it when a
    local build diverges from the committed CRC, even if download fallback
    fails. Drop BuildSegmentResult and BaseTableDataManager increments so
    the metric stays scoped to the local-build path.
    
    Co-authored-by: Cursor <[email protected]>
    
    ---------
    
    Co-authored-by: Cursor <[email protected]>
---
 .../apache/pinot/common/metrics/ServerMeter.java   |   1 +
 .../realtime/RealtimeSegmentDataManager.java       |  41 +++--
 .../data/manager/BaseTableDataManagerTest.java     |  18 ++
 .../realtime/RealtimeSegmentDataManagerTest.java   | 181 +++++++++++++++++----
 4 files changed, 195 insertions(+), 46 deletions(-)

diff --git 
a/pinot-common/src/main/java/org/apache/pinot/common/metrics/ServerMeter.java 
b/pinot-common/src/main/java/org/apache/pinot/common/metrics/ServerMeter.java
index a02882ce34b..ce15566628d 100644
--- 
a/pinot-common/src/main/java/org/apache/pinot/common/metrics/ServerMeter.java
+++ 
b/pinot-common/src/main/java/org/apache/pinot/common/metrics/ServerMeter.java
@@ -103,6 +103,7 @@ public enum ServerMeter implements AbstractMetrics.Meter {
   SEGMENT_DOWNLOAD_FAILURES("segments", false),
   SEGMENT_DOWNLOAD_FROM_REMOTE_FAILURES("segments", false),
   SEGMENT_DOWNLOAD_FROM_PEERS_FAILURES("segments", false),
+  SEGMENT_BUILD_WITH_CRC_MISMATCH("segments", false),
   SEGMENT_BUILD_FAILURE("segments", false),
   SEGMENT_UPLOAD_FAILURE("segments", false),
   SEGMENT_UPLOAD_SUCCESS("segments", false),
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeSegmentDataManager.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeSegmentDataManager.java
index df626b8e6c0..df9b875fed3 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeSegmentDataManager.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeSegmentDataManager.java
@@ -40,6 +40,7 @@ import java.util.concurrent.atomic.AtomicReference;
 import java.util.concurrent.locks.Lock;
 import java.util.function.BooleanSupplier;
 import javax.annotation.Nullable;
+import org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.pinot.common.Utils;
@@ -1410,12 +1411,15 @@ public class RealtimeSegmentDataManager extends 
SegmentDataManager {
   ///
   /// A locally-built segment can diverge in CRC from the one the winning 
replica committed (e.g. different docId
   /// ordering); swapping in a divergent copy corrupts upsert metadata and 
leaves replicas inconsistent otherwise. So
-  /// when `committedSegmentZKMetadata` carries a CRC, a mismatch discards the 
local build (returns `false`) and the
+  /// when `committedSegmentZKMetadata` carries a CRC, a mismatch discards the 
local build and the
   /// caller downloads the committed segment. Skipped for pauseless tables and 
segments whose CRC is not yet set (the
   /// COMMITTING window), so we never download a not-yet-uploaded segment (see 
PR #17885).
   ///
+  /// Confirmed CRC mismatches are counted via {@link 
ServerMeter#SEGMENT_BUILD_WITH_CRC_MISMATCH} regardless of
+  /// whether the caller's download fallback succeeds. Build failures and 
CRC-read errors are not counted.
+  ///
   /// @param committedSegmentZKMetadata committed ZK metadata carrying the 
CRC, or `null` to skip the check
-  /// @return `true` if built and replaced locally; `false` if the build 
failed or was rejected on a CRC mismatch
+  /// @return `true` if the consuming segment was replaced with the local 
copy; `false` if the caller should download
   protected boolean buildSegmentAndReplace(@Nullable SegmentZKMetadata 
committedSegmentZKMetadata)
       throws Exception {
     SegmentBuildDescriptor descriptor;
@@ -1429,10 +1433,19 @@ public class RealtimeSegmentDataManager extends 
SegmentDataManager {
     }
     boolean crcCheckEnabled = committedSegmentZKMetadata != null && 
committedSegmentZKMetadata.getCrc() >= 0
         && !PauselessConsumptionUtils.isPauselessEnabled(_tableConfig);
-    if (crcCheckEnabled && 
!isLocalSegmentCrcMatchingZk(committedSegmentZKMetadata)) {
-      _segmentLogger.warn("Locally-built segment: {} CRC does not match 
committed CRC: {} in zk. "
-          + "Skipping local build to replace", _segmentNameStr, 
committedSegmentZKMetadata.getCrc());
-      return false;
+    if (crcCheckEnabled) {
+      try {
+        if (!isLocalSegmentCrcMatchingZk(committedSegmentZKMetadata)) {
+          _segmentLogger.warn("Locally-built segment: {} CRC does not match 
committed CRC: {} in zk. "
+              + "Skipping local build to replace", _segmentNameStr, 
committedSegmentZKMetadata.getCrc());
+          _serverMetrics.addMeteredTableValue(_tableNameWithType, 
ServerMeter.SEGMENT_BUILD_WITH_CRC_MISMATCH, 1);
+          return false;
+        }
+      } catch (Exception e) {
+        _segmentLogger.warn("Failed to read CRC of locally-built segment: {}; 
treating as a build failure to download "
+            + "the committed segment", _segmentNameStr, e);
+        return false;
+      }
     }
     // On a CRC match use the committed metadata; otherwise the 
construction-time metadata.
     _realtimeTableDataManager.replaceConsumingSegment(_segmentNameStr,
@@ -1440,19 +1453,13 @@ public class RealtimeSegmentDataManager extends 
SegmentDataManager {
     return true;
   }
 
-  /// Whether the just-built local segment's CRC matches the committed CRC in 
ZK. Returns `false` if the local metadata
-  /// cannot be read, so the caller downloads the committed segment rather 
than failing the transition.
+  /// Whether the just-built local segment's CRC matches the committed CRC in 
ZK.
   @VisibleForTesting
-  protected boolean isLocalSegmentCrcMatchingZk(SegmentZKMetadata 
committedSegmentZKMetadata) {
+  protected boolean isLocalSegmentCrcMatchingZk(SegmentZKMetadata 
committedSegmentZKMetadata)
+      throws IOException, ConfigurationException {
     File indexDir = new File(_resourceDataDir, _segmentNameStr);
-    try {
-      SegmentMetadataImpl localMetadata = new SegmentMetadataImpl(indexDir);
-      return BaseTableDataManager.hasSameCRC(committedSegmentZKMetadata, 
localMetadata);
-    } catch (Exception e) {
-      _segmentLogger.warn("Failed to read CRC of locally-built segment: {}; 
treating as CRC mismatch to download the "
-          + "committed segment", _segmentNameStr, e);
-      return false;
-    }
+    SegmentMetadataImpl localMetadata = new SegmentMetadataImpl(indexDir);
+    return BaseTableDataManager.hasSameCRC(committedSegmentZKMetadata, 
localMetadata);
   }
 
   /// Closes the stream consumer so that no more data can be consumed into 
this segment. Does NOT release the consumer
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/BaseTableDataManagerTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/BaseTableDataManagerTest.java
index 45136d68169..b5ce0eca72a 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/BaseTableDataManagerTest.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/BaseTableDataManagerTest.java
@@ -939,6 +939,24 @@ public class BaseTableDataManagerTest {
     assertFalse(tableDataManager.getSegmentDataDir(segmentName).exists());
   }
 
+  @Test
+  public void testReplaceSegmentSkippedWhenCrcCheckDisabled()
+      throws Exception {
+    SegmentZKMetadata zkMetadata = createRawSegment(SegmentVersion.v3, 5);
+    InstanceDataManagerConfig instanceDataManagerConfig = 
createDefaultInstanceDataManagerConfig();
+    
when(instanceDataManagerConfig.shouldCheckCRCOnSegmentLoad()).thenReturn(false);
+
+    BaseTableDataManager tableDataManager = 
createTableManager(instanceDataManagerConfig);
+    File indexDir = createSegment(SegmentVersion.v3, 3);
+    tableDataManager.addNewOnlineSegment(zkMetadata, new IndexLoadingConfig());
+    assertEquals(new SegmentMetadataImpl(indexDir).getTotalDocs(), 3);
+
+    ImmutableSegmentDataManager segmentDataManager = 
createImmutableSegmentDataManager(SEGMENT_NAME, 0);
+    tableDataManager.replaceSegmentIfCrcMismatch(segmentDataManager, 
zkMetadata, new IndexLoadingConfig());
+    // CRC check disabled: local copy is kept even though CRCs differ.
+    assertEquals(new SegmentMetadataImpl(indexDir).getTotalDocs(), 3);
+  }
+
   @Test
   public void testReplaceSegmentNewDataWithAsyncSegmentRefresh()
       throws Exception {
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/RealtimeSegmentDataManagerTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/RealtimeSegmentDataManagerTest.java
index 64f1b96efb9..f3e3d3dab3a 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/RealtimeSegmentDataManagerTest.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/RealtimeSegmentDataManagerTest.java
@@ -37,6 +37,7 @@ import java.util.concurrent.locks.Lock;
 import java.util.function.BooleanSupplier;
 import java.util.function.Supplier;
 import javax.annotation.Nullable;
+import org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.commons.io.FileUtils;
 import org.apache.helix.HelixManager;
 import org.apache.pinot.common.metadata.segment.SegmentZKMetadata;
@@ -102,6 +103,9 @@ import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.assertTrue;
 
 
 // TODO Re-write this test using the stream abstraction
@@ -778,7 +782,9 @@ public class RealtimeSegmentDataManagerTest {
     }
 
     // Test downloadAndReplace is called after buildAndReplace fails.
-    try (FakeRealtimeSegmentDataManager segmentDataManager = 
createFakeSegmentManager()) {
+    ServerMetrics buildFailureServerMetrics = mock(ServerMetrics.class);
+    try (FakeRealtimeSegmentDataManager segmentDataManager =
+        createFakeSegmentManager(false, new TimeSupplier(), null, null, null, 
buildFailureServerMetrics)) {
       segmentDataManager._failSegmentBuildAndReplace = true;
       segmentDataManager._stopWaitTimeMs = 0;
       segmentDataManager._state.set(segmentDataManager, 
RealtimeSegmentDataManager.State.HOLDING);
@@ -786,6 +792,8 @@ public class RealtimeSegmentDataManagerTest {
       segmentDataManager.goOnlineFromConsuming(metadata);
       Assert.assertTrue(segmentDataManager._downloadAndReplaceCalled);
       Assert.assertTrue(segmentDataManager._buildAndReplaceCalled);
+      verify(buildFailureServerMetrics, never())
+          .addMeteredTableValue(anyString(), 
eq(ServerMeter.SEGMENT_BUILD_WITH_CRC_MISMATCH), eq(1L));
     }
   }
 
@@ -892,11 +900,15 @@ public class RealtimeSegmentDataManagerTest {
     long finalOffsetValue = START_OFFSET_VALUE + 600;
 
     // Upsert + committed CRC set + local CRC mismatch -> discard local build, 
download the committed segment.
-    try (FakeRealtimeSegmentDataManager segmentDataManager = 
createFakeSegmentManager()) {
+    ServerMetrics crcMismatchServerMetrics = mock(ServerMetrics.class);
+    try (FakeRealtimeSegmentDataManager segmentDataManager =
+        createFakeSegmentManager(false, new TimeSupplier(), null, null, null, 
crcMismatchServerMetrics)) {
       
when(segmentDataManager._tableDataManager.isUpsertEnabled()).thenReturn(true);
       runGoOnlineForCrcGuard(segmentDataManager, crcMetadata(finalOffsetValue, 
12345L), false, finalOffsetValue);
-      Assert.assertTrue(segmentDataManager._buildAndReplaceCalled);
-      Assert.assertTrue(segmentDataManager._downloadAndReplaceCalled);
+      assertTrue(segmentDataManager._buildAndReplaceCalled);
+      assertTrue(segmentDataManager._downloadAndReplaceCalled);
+      verify(crcMismatchServerMetrics)
+          .addMeteredTableValue(anyString(), 
eq(ServerMeter.SEGMENT_BUILD_WITH_CRC_MISMATCH), eq(1L));
     }
 
     // Non-upsert + committed CRC set + local CRC mismatch -> discard local 
build, download the committed segment.
@@ -908,51 +920,175 @@ public class RealtimeSegmentDataManagerTest {
     }
 
     // Upsert + committed CRC set + local CRC match -> build locally, no 
download, swap in committed metadata.
-    try (FakeRealtimeSegmentDataManager segmentDataManager = 
createFakeSegmentManager()) {
+    ServerMetrics matchingUpsertServerMetrics = mock(ServerMetrics.class);
+    try (FakeRealtimeSegmentDataManager segmentDataManager =
+        createFakeSegmentManager(false, new TimeSupplier(), null, null, null, 
matchingUpsertServerMetrics)) {
       
when(segmentDataManager._tableDataManager.isUpsertEnabled()).thenReturn(true);
       SegmentZKMetadata committedMetadata = crcMetadata(finalOffsetValue, 
12345L);
       runGoOnlineForCrcGuard(segmentDataManager, committedMetadata, true, 
finalOffsetValue);
       Assert.assertTrue(segmentDataManager._buildAndReplaceCalled);
       Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled);
+      verify(matchingUpsertServerMetrics, never())
+          .addMeteredTableValue(anyString(), 
eq(ServerMeter.SEGMENT_BUILD_WITH_CRC_MISMATCH), eq(1L));
       verify(segmentDataManager._tableDataManager)
           .replaceConsumingSegment(eq(SEGMENT_NAME_STR), 
same(committedMetadata));
     }
 
     // Non-upsert + committed CRC set + local CRC match -> build locally, no 
download, swap in committed metadata.
-    try (FakeRealtimeSegmentDataManager segmentDataManager = 
createFakeSegmentManager()) {
+    ServerMetrics matchingNonUpsertServerMetrics = mock(ServerMetrics.class);
+    try (FakeRealtimeSegmentDataManager segmentDataManager =
+        createFakeSegmentManager(false, new TimeSupplier(), null, null, null, 
matchingNonUpsertServerMetrics)) {
       
when(segmentDataManager._tableDataManager.isUpsertEnabled()).thenReturn(false);
       SegmentZKMetadata committedMetadata = crcMetadata(finalOffsetValue, 
12345L);
       runGoOnlineForCrcGuard(segmentDataManager, committedMetadata, true, 
finalOffsetValue);
       Assert.assertTrue(segmentDataManager._buildAndReplaceCalled);
       Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled);
+      verify(matchingNonUpsertServerMetrics, never())
+          .addMeteredTableValue(anyString(), 
eq(ServerMeter.SEGMENT_BUILD_WITH_CRC_MISMATCH), eq(1L));
       verify(segmentDataManager._tableDataManager)
           .replaceConsumingSegment(eq(SEGMENT_NAME_STR), 
same(committedMetadata));
     }
 
     // Committed CRC unset (-1) -> guard skipped, build locally with 
construction-time metadata.
-    try (FakeRealtimeSegmentDataManager segmentDataManager = 
createFakeSegmentManager()) {
+    ServerMetrics unsetCrcServerMetrics = mock(ServerMetrics.class);
+    try (FakeRealtimeSegmentDataManager segmentDataManager =
+        createFakeSegmentManager(false, new TimeSupplier(), null, null, null, 
unsetCrcServerMetrics)) {
       
when(segmentDataManager._tableDataManager.isUpsertEnabled()).thenReturn(false);
       SegmentZKMetadata committedMetadata = crcMetadata(finalOffsetValue, -1L);
       runGoOnlineForCrcGuard(segmentDataManager, committedMetadata, false, 
finalOffsetValue);
       Assert.assertTrue(segmentDataManager._buildAndReplaceCalled);
       Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled);
+      verify(unsetCrcServerMetrics, never())
+          .addMeteredTableValue(anyString(), 
eq(ServerMeter.SEGMENT_BUILD_WITH_CRC_MISMATCH), eq(1L));
       verify(segmentDataManager._tableDataManager)
           .replaceConsumingSegment(eq(SEGMENT_NAME_STR), argThat(m -> m != 
committedMetadata));
     }
 
     // Pauseless table -> guard skipped, build locally with construction-time 
metadata.
+    ServerMetrics pauselessServerMetrics = mock(ServerMetrics.class);
     try (FakeRealtimeSegmentDataManager segmentDataManager =
-        createFakeSegmentManager(false, new TimeSupplier(), null, null, 
createPauselessTableConfig())) {
+        createFakeSegmentManager(false, new TimeSupplier(), null, null, 
createPauselessTableConfig(),
+            pauselessServerMetrics)) {
       
when(segmentDataManager._tableDataManager.isUpsertEnabled()).thenReturn(false);
       SegmentZKMetadata committedMetadata = crcMetadata(finalOffsetValue, 
12345L);
       runGoOnlineForCrcGuard(segmentDataManager, committedMetadata, false, 
finalOffsetValue);
       Assert.assertTrue(segmentDataManager._buildAndReplaceCalled);
       Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled);
+      verify(pauselessServerMetrics, never())
+          .addMeteredTableValue(anyString(), 
eq(ServerMeter.SEGMENT_BUILD_WITH_CRC_MISMATCH), eq(1L));
       verify(segmentDataManager._tableDataManager)
           .replaceConsumingSegment(eq(SEGMENT_NAME_STR), argThat(m -> m != 
committedMetadata));
     }
   }
 
+  @Test
+  public void testCrcBuildMismatchMetricForCatchup()
+      throws Exception {
+    long finalOffsetValue = START_OFFSET_VALUE + 600;
+    LongMsgOffset finalOffset = new LongMsgOffset(finalOffsetValue);
+    SegmentZKMetadata metadata = crcMetadata(finalOffsetValue, 12345L);
+    ServerMetrics serverMetrics = mock(ServerMetrics.class);
+    try (FakeRealtimeSegmentDataManager segmentDataManager =
+        createFakeSegmentManager(false, new TimeSupplier(), null, null, null, 
serverMetrics)) {
+      segmentDataManager._useRealBuildAndReplace = true;
+      segmentDataManager._localSegmentCrcMatchesZk = false;
+      segmentDataManager.getConsumerSemaphoreAcquired().set(true);
+      segmentDataManager._stopWaitTimeMs = 0;
+      segmentDataManager._state.set(segmentDataManager, 
RealtimeSegmentDataManager.State.CATCHING_UP);
+      segmentDataManager._consumeOffsets.add(finalOffset);
+
+      segmentDataManager.goOnlineFromConsuming(metadata);
+
+      assertTrue(segmentDataManager._buildAndReplaceCalled);
+      assertTrue(segmentDataManager._downloadAndReplaceCalled);
+      verify(serverMetrics)
+          .addMeteredTableValue(anyString(), 
eq(ServerMeter.SEGMENT_BUILD_WITH_CRC_MISMATCH), eq(1L));
+    }
+  }
+
+  @Test
+  public void testCrcBuildMismatchMetricNotEmittedForCatchupBuildFailure()
+      throws Exception {
+    long finalOffsetValue = START_OFFSET_VALUE + 600;
+    SegmentZKMetadata metadata = crcMetadata(finalOffsetValue, 12345L);
+    ServerMetrics serverMetrics = mock(ServerMetrics.class);
+    try (FakeRealtimeSegmentDataManager segmentDataManager =
+        createFakeSegmentManager(false, new TimeSupplier(), null, null, null, 
serverMetrics)) {
+      segmentDataManager._failSegmentBuildAndReplace = true;
+      segmentDataManager.getConsumerSemaphoreAcquired().set(true);
+      segmentDataManager._stopWaitTimeMs = 0;
+      segmentDataManager._state.set(segmentDataManager, 
RealtimeSegmentDataManager.State.CATCHING_UP);
+      segmentDataManager._consumeOffsets.add(new 
LongMsgOffset(finalOffsetValue));
+
+      segmentDataManager.goOnlineFromConsuming(metadata);
+
+      assertTrue(segmentDataManager._buildAndReplaceCalled);
+      assertTrue(segmentDataManager._downloadAndReplaceCalled);
+      verify(serverMetrics, never())
+          .addMeteredTableValue(anyString(), 
eq(ServerMeter.SEGMENT_BUILD_WITH_CRC_MISMATCH), eq(1L));
+    }
+  }
+
+  @Test
+  public void testCrcBuildMismatchMetricNotEmittedForCrcReadError()
+      throws Exception {
+    long finalOffsetValue = START_OFFSET_VALUE + 600;
+    ServerMetrics serverMetrics = mock(ServerMetrics.class);
+    try (FakeRealtimeSegmentDataManager segmentDataManager =
+        createFakeSegmentManager(false, new TimeSupplier(), null, null, null, 
serverMetrics)) {
+      segmentDataManager._useRealCrcCheck = true;
+      FileUtils.deleteQuietly(new File(new File(TEMP_DIR, 
REALTIME_TABLE_NAME), SEGMENT_NAME_STR));
+      runGoOnlineForCrcGuard(segmentDataManager, crcMetadata(finalOffsetValue, 
12345L), false, finalOffsetValue);
+
+      assertTrue(segmentDataManager._buildAndReplaceCalled);
+      assertTrue(segmentDataManager._downloadAndReplaceCalled);
+      verify(serverMetrics, never())
+          .addMeteredTableValue(anyString(), 
eq(ServerMeter.SEGMENT_BUILD_WITH_CRC_MISMATCH), eq(1L));
+    }
+  }
+
+  @Test
+  public void testCrcBuildMismatchMetricEmittedWhenMatchedOffsetDownloadFails()
+      throws Exception {
+    long finalOffsetValue = START_OFFSET_VALUE + 600;
+    ServerMetrics serverMetrics = mock(ServerMetrics.class);
+    try (FakeRealtimeSegmentDataManager segmentDataManager =
+        createFakeSegmentManager(false, new TimeSupplier(), null, null, null, 
serverMetrics)) {
+      segmentDataManager._failDownloadAndReplace = true;
+
+      assertThrows(IOException.class, () ->
+          runGoOnlineForCrcGuard(segmentDataManager, 
crcMetadata(finalOffsetValue, 12345L), false, finalOffsetValue));
+
+      // Confirmed mismatch is counted even when the download fallback fails.
+      verify(serverMetrics)
+          .addMeteredTableValue(anyString(), 
eq(ServerMeter.SEGMENT_BUILD_WITH_CRC_MISMATCH), eq(1L));
+    }
+  }
+
+  @Test
+  public void testCrcBuildMismatchMetricEmittedWhenCatchupDownloadFails()
+      throws Exception {
+    long finalOffsetValue = START_OFFSET_VALUE + 600;
+    SegmentZKMetadata metadata = crcMetadata(finalOffsetValue, 12345L);
+    ServerMetrics serverMetrics = mock(ServerMetrics.class);
+    try (FakeRealtimeSegmentDataManager segmentDataManager =
+        createFakeSegmentManager(false, new TimeSupplier(), null, null, null, 
serverMetrics)) {
+      segmentDataManager._useRealBuildAndReplace = true;
+      segmentDataManager._localSegmentCrcMatchesZk = false;
+      segmentDataManager._failDownloadAndReplace = true;
+      segmentDataManager.getConsumerSemaphoreAcquired().set(true);
+      segmentDataManager._stopWaitTimeMs = 0;
+      segmentDataManager._state.set(segmentDataManager, 
RealtimeSegmentDataManager.State.CATCHING_UP);
+      segmentDataManager._consumeOffsets.add(new 
LongMsgOffset(finalOffsetValue));
+
+      assertThrows(IOException.class, () -> 
segmentDataManager.goOnlineFromConsuming(metadata));
+
+      // Confirmed mismatch is counted even when the download fallback fails.
+      verify(serverMetrics)
+          .addMeteredTableValue(anyString(), 
eq(ServerMeter.SEGMENT_BUILD_WITH_CRC_MISMATCH), eq(1L));
+    }
+  }
+
   private void runGoOnlineForCrcGuard(FakeRealtimeSegmentDataManager 
segmentDataManager, SegmentZKMetadata metadata,
       boolean localCrcMatchesZk, long currentOffsetValue)
       throws Exception {
@@ -1082,26 +1218,11 @@ public class RealtimeSegmentDataManagerTest {
 
       SegmentZKMetadata matchingMetadata = new 
SegmentZKMetadata(SEGMENT_NAME_STR);
       matchingMetadata.setCrc(localCrc);
-      
Assert.assertTrue(segmentDataManager.isLocalSegmentCrcMatchingZk(matchingMetadata));
+      
assertTrue(segmentDataManager.isLocalSegmentCrcMatchingZk(matchingMetadata));
 
       SegmentZKMetadata mismatchingMetadata = new 
SegmentZKMetadata(SEGMENT_NAME_STR);
       mismatchingMetadata.setCrc(localCrc + 1);
-      
Assert.assertFalse(segmentDataManager.isLocalSegmentCrcMatchingZk(mismatchingMetadata));
-    }
-  }
-
-  // When the locally-built segment cannot be read (e.g. missing/corrupt), the 
CRC check must report a mismatch so the
-  // ONLINE transition downloads the committed segment instead of throwing.
-  @Test
-  public void 
testIsLocalSegmentCrcMatchingZkTreatsUnreadableSegmentAsMismatch()
-      throws Exception {
-    try (FakeRealtimeSegmentDataManager segmentDataManager = 
createFakeSegmentManager()) {
-      segmentDataManager._useRealCrcCheck = true;
-      // No segment was built under the resource dir, so the metadata read 
fails.
-      FileUtils.deleteQuietly(new File(new File(TEMP_DIR, 
REALTIME_TABLE_NAME), SEGMENT_NAME_STR));
-      SegmentZKMetadata metadata = new SegmentZKMetadata(SEGMENT_NAME_STR);
-      metadata.setCrc(12345L);
-      
Assert.assertFalse(segmentDataManager.isLocalSegmentCrcMatchingZk(metadata));
+      
assertFalse(segmentDataManager.isLocalSegmentCrcMatchingZk(mismatchingMetadata));
     }
   }
 
@@ -1671,11 +1792,8 @@ public class RealtimeSegmentDataManagerTest {
     public Field _stopReasonCode;
     public Field _segmentBuildFailedWithDeterministicError;
     public boolean _failSegmentBuildAndReplace = false;
-    // When set, buildSegmentAndReplace runs the real implementation 
(including the upsert CRC guard) instead of being
-    // short-circuited, and isLocalSegmentCrcMatchingZk returns 
_localSegmentCrcMatchesZk.
     public boolean _useRealBuildAndReplace = false;
     public boolean _localSegmentCrcMatchesZk = true;
-    // When set, isLocalSegmentCrcMatchingZk runs the real on-disk metadata 
read + CRC comparison.
     public boolean _useRealCrcCheck = false;
     // When set, buildSegmentInternal releases the consumer semaphore like the 
real build does when the parallel
     // consumption policy allows consuming during build.
@@ -1689,6 +1807,7 @@ public class RealtimeSegmentDataManagerTest {
     public boolean _buildSegmentCalled = false;
     public boolean _failSegmentBuild = false;
     public boolean _buildAndReplaceCalled = false;
+    public boolean _failDownloadAndReplace = false;
     public int _stopWaitTimeMs = 100;
     private boolean _downloadAndReplaceCalled = false;
     private boolean _notifySegmentBuildFailedWithDeterministicErrorCalled = 
false;
@@ -1897,7 +2016,8 @@ public class RealtimeSegmentDataManagerTest {
     }
 
     @Override
-    protected boolean isLocalSegmentCrcMatchingZk(SegmentZKMetadata 
committedSegmentZKMetadata) {
+    protected boolean isLocalSegmentCrcMatchingZk(SegmentZKMetadata 
committedSegmentZKMetadata)
+        throws IOException, ConfigurationException {
       if (_useRealCrcCheck) {
         return super.isLocalSegmentCrcMatchingZk(committedSegmentZKMetadata);
       }
@@ -1943,6 +2063,9 @@ public class RealtimeSegmentDataManagerTest {
     protected void downloadSegmentAndReplace(SegmentZKMetadata metadata)
         throws Exception {
       terminateLoopIfNecessary();
+      if (_failDownloadAndReplace) {
+        throw new IOException("download failed");
+      }
       _downloadAndReplaceCalled = true;
       if (_useRealDownloadAndReplace) {
         super.downloadSegmentAndReplace(metadata);


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

Reply via email to