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


##########
multi-stage-query/src/main/java/org/apache/druid/msq/querykit/scan/ScanQueryFrameProcessor.java:
##########
@@ -557,19 +562,29 @@ private long setNextCursor(
     if (query.isLimited()
         && ordering.size() >= query.getOrderBys().size()
         && query.getOrderBys().equals(ordering.subList(0, 
query.getOrderBys().size()))) {
-      cursorPushDownLimit = getQueryOffsetPlusLimit();
+      cursorPushDownLimit = queryOffsetPlusLimit;
     } else {
       cursorPushDownLimit = NO_LIMIT;
     }
 
     return rowsFlushed;
   }
 
+  /**
+   * Whether the limit in {@link #sharedRunningCountForLimit} has been 
reached, or would be reached if
+   * {@code pendingRows} were added.
+   */
+  private boolean isSharedLimitReached(final long pendingRows)
+  {
+    return sharedRunningCountForLimit != null
+           && sharedRunningCountForLimit.get() + pendingRows >= 
queryOffsetPlusLimit;

Review Comment:
   [P2] Shared limit admission is not atomic
   
   **Finding:** Each processor reads the shared count here, but the count is 
incremented only after its frame is written. Concurrent processors can 
therefore all observe remaining capacity, fill and flush rows, and push the 
count beyond offset+limit by roughly one buffered frame per processor; the 
downstream limit may discard those rows, but the scan work and shuffle overhead 
remain.
   
   **Suggestion:** Atomically reserve the remaining row budget or otherwise 
coordinate row admission with accounting, and cover multi-processor execution 
with a regression test.



##########
multi-stage-query/src/main/java/org/apache/druid/msq/querykit/ReadableInputQueue.java:
##########
@@ -303,11 +316,15 @@ private ListenableFuture<ReadableInput> nextSegmentInput()
 
   /**
    * Load the next segment from {@link #loadableSegments} and return a future 
to its reference. Returns null
-   * if {@link #loadableSegments} is empty.
+   * if {@link #loadableSegments} is empty, or if {@link #canSkipRemaining} is 
true.
    */
   @Nullable
   private ListenableFuture<ReadableInput> loadNextSegment()
   {
+    if (canSkipRemaining.getAsBoolean()) {

Review Comment:
   [P2] Segment load can race the skip predicate
   
   **Finding:** The `canSkipRemaining` check occurs before entering the 
synchronized poll-and-acquire block, while the shared count is updated 
concurrently by processors. A thread can observe false, wait for the monitor, 
and then start acquiring another segment after another processor reaches 
offset+limit, handing work to a processor that immediately exits and wasting 
segment I/O and cache resources.
   
   **Suggestion:** Coordinate or recheck the skip decision with the 
synchronized poll-and-acquire operation, and add a concurrent transition test.



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