Jackie-Jiang commented on code in PR #19740:
URL: https://github.com/apache/pinot/pull/19740#discussion_r4331476685


##########
pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManager.java:
##########
@@ -836,17 +904,35 @@ private String createNewSegmentMetadata(TableConfig 
tableConfig,
             + "paused.", committingSegmentName);
         return null;
       }
+      // Another attempt of the same commit start might have created the new 
segment already, and the segment might be
+      // consuming or even committed, so never overwrite its ZK metadata
+      if (resumed) {
+        String existingNewSegmentName = 
findNewSegmentOfEarlierAttempt(realtimeTableName, committingLLCSegment);
+        if (existingNewSegmentName != null) {
+          LOGGER.info("Using existing segment metadata for new segment: {}", 
existingNewSegmentName);
+          return existingNewSegmentName;

Review Comment:
   [P1] Find the retained successor before handling a pause
   
   If the initial commit start creates successor N and its IdealState update 
fails, the new failure handler retains N. If the table/topic is paused before 
the same-offset retry, the pause check above returns null before this lookup. 
The retry can then successfully mark the predecessor ONLINE, but the 
post-update cleanup never sees N because newConsumingSegmentName is null.
   
   The validator later selects N as the latest segment and takes the 
metadata-without-IdealState repair branch. That branch neither checks whether 
its topic is paused nor applies the requested resume offsetCriteria: it can 
restart a paused topic after the completion timeout, or ignore consumeFrom when 
a paused table is resumed.
   
   Please resolve the retained successor before the pause decision so 
successful paused completion can clean up its unused metadata, while preserving 
the upstream partition-eligibility check. A failure → pause → retry → 
validation regression test would cover this.



##########
pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManager.java:
##########
@@ -659,51 +659,65 @@ private void commitSegmentMetadataInternal(String 
realtimeTableName,
     // Step-1: Update PROPERTYSTORE
     LOGGER.info("Committing segment metadata for segment: {}", 
committingSegmentName);
     long startTimeNs1 = System.nanoTime();
-    SegmentZKMetadata committingSegmentZKMetadata = toCommitting
-        ? updateSegmentZKMetadataToCommitting(realtimeTableName, 
committingSegmentDescriptor)
-        : updateSegmentZKMetadataToDone(realtimeTableName, 
committingSegmentDescriptor, Status.IN_PROGRESS);
+    boolean resumed = toCommitting && 
isCommitStartResumable(realtimeTableName, committingSegmentDescriptor);
+    SegmentZKMetadata committingSegmentZKMetadata;
+    if (resumed) {
+      committingSegmentZKMetadata = getSegmentZKMetadata(realtimeTableName, 
committingSegmentName);

Review Comment:
   Optional simplification: combine resumability checking with the COMMITTING 
transition
   
   A prepareCommitStart helper could read SegmentZKMetadata and Stat once, 
accept a valid same-offset COMMITTING state or perform the existing 
version-checked IN_PROGRESS → COMMITTING transition, and return the metadata 
plus resume information. The saved modification time could also supply the 
successor probe's lower bound.
   
   This would remove repeated metadata reads and the current flow where 
isCommitStartResumable returns false for an invalid COMMITTING state only for 
updateSegmentZKMetadataToCommitting to read it again and reject it. Preserve 
the original modification time when resuming and keep the fresh deadline check 
inside the IdealState updater.



##########
pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManager.java:
##########
@@ -806,11 +820,65 @@ private SegmentZKMetadata 
updateSegmentZKMetadataToCommitting(String realtimeTab
     return segmentZKMetadata;
   }
 
+  /// Returns `true` if a pauseless commit start can resume an earlier attempt 
that moved the segment to COMMITTING
+  /// with the same end offset, but did not complete, e.g. because the 
IdealState update failed or the controller
+  /// stopped. Steps 2 and 3 are idempotent for the same segment and end 
offset: the resumed attempt uses the new
+  /// segment created by an earlier attempt if any (see 
findNewSegmentOfEarlierAttempt), new segment ZK metadata is only
+  /// created if absent, and the IdealState update is skipped when it is 
already applied. Once the max segment
+  /// completion time is exceeded, RealtimeSegmentValidationManager repairs 
the segment instead.
+  private boolean isCommitStartResumable(String realtimeTableName,
+      CommittingSegmentDescriptor committingSegmentDescriptor) {
+    String segmentName = committingSegmentDescriptor.getSegmentName();
+    SegmentZKMetadata segmentZKMetadata = 
getSegmentZKMetadata(realtimeTableName, segmentName);
+    if (segmentZKMetadata.getStatus() != Status.COMMITTING) {
+      return false;
+    }
+    String endOffset = segmentZKMetadata.getEndOffset();
+    if (!committingSegmentDescriptor.getNextOffset().equals(endOffset)) {
+      LOGGER.warn("Cannot resume commit start for segment: {} with end offset: 
{}, segment is COMMITTING with end "
+          + "offset: {}", segmentName, 
committingSegmentDescriptor.getNextOffset(), endOffset);
+      return false;
+    }
+    if (isExceededMaxSegmentCompletionTime(realtimeTableName, segmentName, 
getCurrentTimeMs())) {
+      LOGGER.warn("Cannot resume commit start for segment: {} after the max 
segment completion time", segmentName);
+      return false;
+    }
+    LOGGER.info("Resuming commit start for segment: {} which is COMMITTING 
with end offset: {}", segmentName,
+        endOffset);
+    return true;
+  }
+
+  /// Returns the new segment created by an earlier attempt of a resumed 
commit start, or `null` if there is none.
+  ///
+  /// The new segment is named after its creation time with minute 
granularity, and is created between step 1 of the
+  /// first attempt (the modification time of the committing segment ZK 
metadata) and now, which is bounded by the max
+  /// segment completion time. Probes the names for every minute in between, 
with one minute of slack on both ends for
+  /// clock differences between controllers and ZK. This relies on nothing 
rewriting the committing segment ZK metadata
+  /// while it stays COMMITTING, as a later modification time would skip the 
minutes in which the new segment was
+  /// created.
+  @Nullable
+  private String findNewSegmentOfEarlierAttempt(String realtimeTableName, 
LLCSegmentName committingLLCSegment) {
+    Stat stat = new Stat();
+    getSegmentZKMetadata(realtimeTableName, 
committingLLCSegment.getSegmentName(), stat);
+    long oneMinuteMs = TimeUnit.MINUTES.toMillis(1);
+    long startTimeMs = Math.floorDiv(stat.getMtime(), oneMinuteMs) * 
oneMinuteMs - oneMinuteMs;
+    long endTimeMs = Math.floorDiv(getCurrentTimeMs(), oneMinuteMs) * 
oneMinuteMs + oneMinuteMs;
+    for (long timeMs = startTimeMs; timeMs <= endTimeMs; timeMs += 
oneMinuteMs) {
+      String segmentName = new 
LLCSegmentName(committingLLCSegment.getTableName(),
+          committingLLCSegment.getPartitionGroupId(), 
committingLLCSegment.getSequenceNumber() + 1,
+          timeMs).getSegmentName();
+      if (segmentZKMetadataExists(realtimeTableName, segmentName)) {
+        return segmentName;
+      }
+    }
+    return null;
+  }
+
   // Step 2: Create new segment metadata
   @Nullable
   private String createNewSegmentMetadata(TableConfig tableConfig,
       CommittingSegmentDescriptor committingSegmentDescriptor, 
SegmentZKMetadata committingSegmentZKMetadata,
-      InstancePartitions instancePartitions) {
+      InstancePartitions instancePartitions, boolean createIfAbsent, boolean 
resumed) {

Review Comment:
   Optional simplification: separate successor recovery from fresh creation
   
   Keep recovery of the earlier attempt's successor in the pauseless 
preparation flow, and let createNewSegmentMetadata handle fresh creation when 
necessary. This would remove its resumed parameter and make the retained 
segment's identity available for cleanup even when the retry observes a pause, 
addressing the ordering issue in the other comment.
   
   Preserve partition eligibility: finding an existing successor must not by 
itself authorize assigning it after its upstream partition disappears. I would 
retain atomic create-if-absent, the idempotent IdealState updater, and the 
bounded timestamp probe. Replacing the probe with a persisted successor 
identity would require a larger metadata-protocol change.



-- 
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