suvodeep-pyne commented on code in PR #19909:
URL: https://github.com/apache/pinot/pull/19909#discussion_r4339385988


##########
pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManager.java:
##########
@@ -3020,13 +3020,13 @@ public void repairSegmentsInErrorState(TableConfig 
tableConfig,
       if (status == Status.IN_PROGRESS && 
segmentsInErrorStateInAllReplicas.contains(segmentName)) {
         LOGGER.info("Resetting IN_PROGRESS segment: {} in table: {} since all 
replicas are in ERROR state.",
             segmentName, realtimeTableName);
-        _helixResourceManager.resetSegment(realtimeTableName, segmentName, 
null);
+        resetErrorReplicas(realtimeTableName, segmentName, currentStateMap);
         continue;
       }
 
       // 3) For all other non-IN_PROGRESS statuses with any ERROR replica, 
trigger a reset to download/refresh
       if (status != Status.IN_PROGRESS) {

Review Comment:
   Intentional, and unchanged from master on every table: master's case 3 also 
excluded IN_PROGRESS. Resetting that replica would re-consume the segment on 
it, which is the problem from your first comment on dedup and partial-upsert 
tables. Waiting for the commit means the reset then downloads the committed 
segment. With `enforceConsumptionInOrder`, that server's next segment of the 
partition waits until then, as on master.
   
   In a779ff97dd the case has its own branch, with an INFO log saying the 
replicas will be reset once the segment commits. The branch also covers a 
COMMITTING segment whose commit hasn't updated the IdealState yet, since a 
reset would re-consume that one too. It is not counted as unrecoverable, since 
it heals on its own, unless no replica is consuming it at all, for example when 
the others are OFFLINE. Then it is counted. Since 
`PAUSELESS_SEGMENTS_IN_ERROR_COUNT` is back to counting segments with every 
replica in ERROR (see your metric comment), it no longer counts this case 
either. The ERROR replica is in `SEGMENTS_IN_ERROR_STATE` from 
`SegmentStatusChecker`.
   
   On collapsing cases 2 and 3: a reset re-consumes an IN_PROGRESS segment, so 
I kept "every replica in ERROR" as the point where re-consuming is the only way 
forward.



##########
pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManager.java:
##########
@@ -2956,22 +2958,14 @@ public void repairSegmentsInErrorState(TableConfig 
tableConfig,
     boolean repairCommittingSegments =
         
allowRepairOfCommittingSegments(repairErrorSegmentsForPartialUpsertOrDedup, 
tableConfig);
     int segmentsInUnRecoverableState = 0;
-    if (isPauselessTable && !repairCommittingSegments) {
-      // We do not run reingestion for dedup and partial upsert tables in 
pauseless as it can
-      // lead to data inconsistencies
-      _controllerMetrics.setOrUpdateTableGauge(realtimeTableName,
-          ControllerGauge.PAUSELESS_SEGMENTS_IN_UNRECOVERABLE_ERROR_COUNT, 
segmentsInErrorStateInAllReplicas.size());
-      return;
-    } else {
-      LOGGER.info("Repairing error segments in table: {}.", realtimeTableName);
-    }
 
+    // Count segments with any ERROR replica so a single lagging replica is 
visible in the metric
     if (isPauselessTable) {

Review Comment:
   Agreed; reverted in a779ff97dd. `PAUSELESS_SEGMENTS_IN_ERROR_COUNT`, and 
`SEGMENTS_IN_ERROR_STATE` as written by this method, count segments with every 
replica in ERROR again, as on master.
   
   I didn't add a new gauge. A single ERROR replica is already visible through 
`SEGMENTS_IN_ERROR_STATE`, which `SegmentStatusChecker` writes for every table, 
pauseless included, as the number of (segment, instance) ERROR pairs. The only 
change left for this gauge is that dedup and partial-upsert tables now set it 
at all; on master the early return skipped it.



##########
pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManager.java:
##########
@@ -3036,6 +3036,24 @@ public void repairSegmentsInErrorState(TableConfig 
tableConfig,
     }
   }
 
+  /// Resets only the replicas of the segment that are in ERROR state in the 
external view. Healthy replicas are left
+  /// untouched: resetting a healthy replica takes it through OFFLINE, which 
on dedup / upsert tables briefly drops its
+  /// keys from the metadata manager while other replicas keep consuming, and 
can make the only good copy of a
+  /// segment without a deep-store copy unavailable.
+  private void resetErrorReplicas(String realtimeTableName, String 
segmentName, Map<String, String> currentStateMap) {
+    for (Map.Entry<String, String> entry : currentStateMap.entrySet()) {
+      if (SegmentStateModel.ERROR.equals(entry.getValue())) {
+        String instanceName = entry.getKey();
+        try {

Review Comment:
   Agreed. `resetErrorReplicas` (added in a779ff97dd) checks each ERROR replica 
against the IdealState it reads. When it skips one, it logs a WARN naming the 
segment, table and instance. Covered by 
`testResetErrorReplicasResetsOnlyReplicasStillInError` (`instance_3`).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to