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


##########
server/src/main/java/org/apache/druid/client/BrokerServerView.java:
##########
@@ -351,26 +394,81 @@ private void serverRemovedSegment(DruidServerMetadata 
server, DataSegment segmen
       }
 
       if (selector.isEmpty()) {
-        VersionedIntervalTimeline<String, ServerSelector> timeline = 
timelines.get(segment.getDataSource());
-        selectors.remove(segmentId);
-
-        final PartitionChunk<ServerSelector> removedPartition = 
timeline.remove(
-            segment.getInterval(), segment.getVersion(), 
segment.getShardSpec().createChunk(selector)
-        );
-
-        if (removedPartition == null) {
-          log.warn(
-              "Asked to remove timeline entry[interval: %s, version: %s] that 
doesn't exist",
-              segment.getInterval(),
-              segment.getVersion()
+        final long delayMillis = 
segmentWatcherConfig.getSegmentDropDelayMillis();

Review Comment:
   [P1] Delayed drops expose empty selectors to queries
   
   During the delay, the empty ServerSelector remains in the timeline. 
CachingClusteredClient can include that holder, pick() returns null, and 
groupSegmentsByServer omits the segment while computeUncoveredIntervals still 
sees the timeline holder, allowing a partial result without an uncovered 
interval. Remove or mark the holder unavailable during the delay, or make 
query-side uncovered-interval handling treat an empty selector as uncovered.



##########
server/src/main/java/org/apache/druid/client/BrokerServerView.java:
##########
@@ -351,26 +394,81 @@ private void serverRemovedSegment(DruidServerMetadata 
server, DataSegment segmen
       }
 
       if (selector.isEmpty()) {
-        VersionedIntervalTimeline<String, ServerSelector> timeline = 
timelines.get(segment.getDataSource());
-        selectors.remove(segmentId);
-
-        final PartitionChunk<ServerSelector> removedPartition = 
timeline.remove(
-            segment.getInterval(), segment.getVersion(), 
segment.getShardSpec().createChunk(selector)
-        );
-
-        if (removedPartition == null) {
-          log.warn(
-              "Asked to remove timeline entry[interval: %s, version: %s] that 
doesn't exist",
-              segment.getInterval(),
-              segment.getVersion()
+        final long delayMillis = 
segmentWatcherConfig.getSegmentDropDelayMillis();
+        if (delayMillis > 0) {
+          // Schedule a delayed removal to prevent the segment load/drop race 
condition.
+          // When a segment is moved from one historical to another, the 
broker may receive
+          // the drop callback from the old server before the load callback 
from the new one.
+          // By delaying the timeline removal, we give the new server time to 
announce the
+          // segment, preventing the segment from temporarily disappearing 
from the timeline.
+          // See https://github.com/apache/druid/issues/18738
+          final ScheduledFuture<?> pendingRemoval = 
delayedRemovalExecutor.schedule(
+              () -> {
+                if (pendingSegmentRemovals.remove(segmentId, pendingRemoval)) {

Review Comment:
   [P1] Stale timer is not rechecked under lock
   
   The callback removes its future from pendingSegmentRemovals before taking 
the timeline lock. A segment can be re-added in that gap and install a newer 
delayed removal; the old callback can then remove the segment while the newer 
callback sees a pending entry and returns, leaving the freshly re-added segment 
absent from the timeline. Recheck that the callback still owns the current 
pending future under the same lock, or make replacement and removal atomic.



##########
server/src/main/java/org/apache/druid/client/BrokerServerView.java:
##########
@@ -351,26 +394,81 @@ private void serverRemovedSegment(DruidServerMetadata 
server, DataSegment segmen
       }
 
       if (selector.isEmpty()) {
-        VersionedIntervalTimeline<String, ServerSelector> timeline = 
timelines.get(segment.getDataSource());
-        selectors.remove(segmentId);
-
-        final PartitionChunk<ServerSelector> removedPartition = 
timeline.remove(
-            segment.getInterval(), segment.getVersion(), 
segment.getShardSpec().createChunk(selector)
-        );
-
-        if (removedPartition == null) {
-          log.warn(
-              "Asked to remove timeline entry[interval: %s, version: %s] that 
doesn't exist",
-              segment.getInterval(),
-              segment.getVersion()
+        final long delayMillis = 
segmentWatcherConfig.getSegmentDropDelayMillis();
+        if (delayMillis > 0) {
+          // Schedule a delayed removal to prevent the segment load/drop race 
condition.
+          // When a segment is moved from one historical to another, the 
broker may receive
+          // the drop callback from the old server before the load callback 
from the new one.
+          // By delaying the timeline removal, we give the new server time to 
announce the
+          // segment, preventing the segment from temporarily disappearing 
from the timeline.
+          // See https://github.com/apache/druid/issues/18738
+          final ScheduledFuture<?> pendingRemoval = 
delayedRemovalExecutor.schedule(
+              () -> {
+                if (pendingSegmentRemovals.remove(segmentId, pendingRemoval)) {
+                  removeSegmentFromTimeline(segment, selector);
+                }
+              },
+              delayMillis,
+              TimeUnit.MILLISECONDS
+          );
+          final ScheduledFuture<?> previous = 
pendingSegmentRemovals.put(segmentId, pendingRemoval);

Review Comment:
   [P2] Timer can fire before pending state is published
   
   The scheduled callback can run before pendingSegmentRemovals.put completes. 
It then finds no pending entry and returns, after which the future is published 
with no callback left to remove the segment from the timeline. Publish the 
pending state before scheduling, or synchronize publication with callback 
execution.



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