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


##########
pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/PinotFSSegmentUploader.java:
##########
@@ -71,14 +79,12 @@ public URI uploadSegment(File segmentFile, LLCSegmentName 
segmentName, int timeo
     final String rawTableName = 
TableNameBuilder.extractRawTableName(segmentName.getTableName());
     Callable<URI> uploadTask = () -> {
       URI destUri = new URI(StringUtil.join(File.separator, 
_segmentStoreUriStr, segmentName.getTableName(),
-          
SegmentCompletionUtils.generateTmpSegmentFileName(segmentName.getSegmentName())));
+          
SegmentCompletionUtils.generateTmpSegmentFileName(segmentName.getSegmentName(), 
_instanceId)));

Review Comment:
   [P1] Reusing this destination allows a timed-out upload to invalidate a 
successful retry. `future.get()` times out without stopping the worker. In 
`LocalPinotFS.copy()`, both attempts now also share the internal `dest + 
".tmp"` path: T1 can finish copying and stall before publication; T2 copies and 
renames that intermediate to `dest`, then returns success; T1 resumes, renames 
T2's destination to a backup, and fails because T2 already moved the 
intermediate. The controller then cannot find the URI returned by the 
successful retry. Removing the explicit delete does not prevent the 
filesystem's own destructive rename. The controller-mediated upload path has 
the same collision. Please preserve unique attempt destinations or guarantee 
that outstanding writers cannot touch a successfully published upload, and add 
a regression test with overlapping timeout/retry attempts.



##########
pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentCompletionUtils.java:
##########
@@ -30,27 +34,39 @@ private SegmentCompletionUtils() {
   private static final String TMP = ".tmp.";
 
   /// Takes in a segment name, and returns a file name prefix that is used to 
store all attempted uploads of this
-  /// segment when a segment is uploaded using split commit. Each attempt has 
a unique file name suffix
+  /// segment when a segment is uploaded using split commit.
   /// @param segmentName segment name
-  /// @return
+  /// @return temporary segment file name prefix
   public static String getTmpSegmentNamePrefix(String segmentName) {
     return segmentName + TMP;
   }
 
+  /// Mints a leftover-style UUID temp name. Prefer {@link 
#generateTmpSegmentFileName(String, String)} for new uploads.
   public static String generateTmpSegmentFileName(String segmentNameStr) {
-    return getTmpSegmentNamePrefix(segmentNameStr) + UUID.randomUUID();
+    return generateTmpSegmentFileName(segmentNameStr, 
UUID.randomUUID().toString());
+  }
+
+  /// Returns `{segment}.tmp.{instanceId}` so retries from one server reuse a 
single deep-store key.
+  public static String generateTmpSegmentFileName(String segmentNameStr, 
String instanceId) {
+    if (StringUtils.isBlank(segmentNameStr)) {
+      throw new IllegalArgumentException("segmentName is required");
+    }
+    if (!isPathSafeTmpSuffix(instanceId)) {
+      throw new IllegalArgumentException("instanceId must be a non-empty 
path-safe identifier: " + instanceId);
+    }
+    return getTmpSegmentNamePrefix(segmentNameStr) + instanceId;
   }
 
   public static boolean isTmpFile(String uri) {
     String[] splits = StringUtils.splitByWholeSeparator(uri, TMP);
     if (splits.length < 2) {
       return false;
     }
-    try {
-      UUID.fromString(splits[splits.length - 1]);
-      return true;
-    } catch (IllegalArgumentException e) {
-      return false;
-    }
+    // Accept leftover UUID temps and {segment}.tmp.{instanceId}. Reject empty 
or path-like suffixes.
+    return isPathSafeTmpSuffix(splits[splits.length - 1]);

Review Comment:
   [P1] Path safety does not establish that a file is temporary. For a hybrid 
table `events`, a valid permanent OFFLINE segment named `batch.tmp.20260918` is 
stored at `dataDir/events/batch.tmp.20260918` and now matches this predicate. 
Realtime validation passes only `events_REALTIME` metadata to 
`deleteTmpSegments()`, which scans the shared raw-table directory and protects 
only those realtime download URLs. With 
`controller.realtime.segment.tmpFileAsyncDeletionEnabled=true`, the live 
offline file is therefore deleted after the temporary-file retention expires. 
The previous UUID check rejected this name. Please retain strict temporary-file 
recognition, or use an unambiguous temporary namespace, and ensure cleanup 
protects permanent files belonging to both table types. Add a hybrid-table 
regression test for this case.



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