FrankChen021 commented on code in PR #19737:
URL: https://github.com/apache/druid/pull/19737#discussion_r3645238089


##########
indexing-service/src/main/java/org/apache/druid/indexing/common/task/KillUnusedSegmentsTask.java:
##########
@@ -251,62 +258,46 @@ public TaskStatus runTask(TaskToolbox toolbox) throws 
Exception
         );
       }
 
-      // Kill segments. Order is important here:
-      // Retrieve the segment upgrade infos for the batch _before_ the 
segments are nuked
-      // We then want the nuke action to clean up the metadata records 
_before_ the segments are removed from storage.
-      // This helps maintain that we will always have a storage segment if the 
metadata segment is present.
-      // Determine the subset of segments to be killed from deep storage based 
on loadspecs.
-      // If the segment nuke throws an exception, then the segment cleanup is 
abandoned.
-
-      // Determine upgraded segment ids before nuking
-      final Set<String> segmentIds = unusedSegments.stream()
-                                                   .map(DataSegment::getId)
-                                                   .map(SegmentId::toString)
-                                                   
.collect(Collectors.toSet());
-      final Map<String, String> upgradedFromSegmentIds = new HashMap<>();
-      try {
-        upgradedFromSegmentIds.putAll(
-            taskActionClient.submit(
-                new RetrieveUpgradedFromSegmentIdsAction(getDataSource(), 
segmentIds)
-            ).getUpgradedFromSegmentIds()
-        );
-      }
-      catch (Exception e) {
-        LOG.warn(
-            e,
-            "Could not retrieve parent segment ids using task 
action[retrieveUpgradedFromSegmentIds]."
-            + " Overlord may be on an older version."
-        );
-      }
+      // Kill segments - order of steps 1, 2, 3, 4 must remain the same
 
-      // Nuke Segments
-      taskActionClient.submit(new SegmentNukeAction(new 
HashSet<>(unusedSegments)));
-      emitMetric(toolbox.getEmitter(), 
TaskMetrics.SEGMENTS_DELETED_FROM_METADATA_STORE, unusedSegments.size());
+      // 1. Determine parent segment ids of killable unused segments
+      final Map<String, String> upgradedFromSegmentIds
+          = fetchParentIdsForSegments(toolbox, unusedSegmentsPlus);
 
-      // Determine segments to be killed
-      final List<DataSegment> segmentsToBeKilled
-          = getKillableSegments(unusedSegments, upgradedFromSegmentIds, 
usedSegmentLoadSpecs, taskActionClient);
+      // 2. Identify killable segments whose load specs are not shared with 
any other segment
+      final List<DataSegment> segmentsToKillFromDeepStore = 
getKillableSegments(
+          unusedSegments,
+          upgradedFromSegmentIds,
+          usedSegmentLoadSpecs,
+          taskActionClient
+      );
 
+      // 2a. Track segments that cannot be removed from deep store yet
       final Set<DataSegment> segmentsNotKilled = new HashSet<>(unusedSegments);
-      segmentsToBeKilled.forEach(segmentsNotKilled::remove);
-
+      segmentsToKillFromDeepStore.forEach(segmentsNotKilled::remove);
       if (!segmentsNotKilled.isEmpty()) {
         LOG.warn(
-            "Skipping kill of [%d] segments from deep storage as their load 
specs are used by other segments.",
-            segmentsNotKilled.size()
+            "Skipping kill of [%d] segments of datasource[%s] from deep 
storage"
+            + " as their load specs are shared by other segments.",
+            segmentsNotKilled.size(), getDataSource()
         );
       }
 
-      toolbox.getDataSegmentKiller().kill(segmentsToBeKilled);
-      emitMetric(toolbox.getEmitter(), 
TaskMetrics.SEGMENTS_DELETED_FROM_DEEPSTORE, segmentsToBeKilled.size());
+      // 3. Nuke all eligible unused segments

Review Comment:
   [P1] Close the sharing race before deleting deep storage
   
   The new relationship lookup is only a preflight before `SegmentNukeAction`. 
With concurrent locks enabled this task holds a REPLACE lock, which can coexist 
with APPEND publication; an append publish or retry can therefore insert a 
parent or upgraded sibling sharing the load spec after step 2. Step 3 then 
removes the old metadata and step 4 deletes a file still referenced by the 
newly published segment. Keep the preflight, but perform a fail-closed sharing 
recheck after the nuke and before deep-storage deletion (ideally in a 
transaction/critical section that excludes a new publication window).



##########
indexing-service/src/main/java/org/apache/druid/indexing/common/task/KillUnusedSegmentsTask.java:
##########
@@ -411,10 +446,11 @@ private List<DataSegment> getKillableSegments(
       );
       if (response != null && response.getUpgradedToSegmentIds() != null) {
         response.getUpgradedToSegmentIds().forEach((parent, children) -> {
-          if (!CollectionUtils.isNullOrEmpty(children)) {
-            // Do not kill segment if its parent or any of its siblings still 
exist in metadata store
+          if (!segmentIdsBeingKilled.containsAll(children)) {

Review Comment:
   [P1] Fail closed when the relationship response is null
   
   If the action response itself or its `upgradedToSegmentIds` map is null, 
this guard skips the only new safety check and treats every candidate as 
unshared. Both the response DTO and remote conversion permit null, so a 
malformed or missing result can still proceed through metadata and deep-storage 
deletion. Throw before `SegmentNukeAction` unless a non-null map was returned.



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