gortiz commented on code in PR #19353:
URL: https://github.com/apache/pinot/pull/19353#discussion_r3902651419


##########
pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/exchange/BroadcastExchange.java:
##########
@@ -39,8 +52,34 @@ protected BroadcastExchange(List<SendingMailbox> 
sendingMailboxes, BlockSplitter
 
   @Override
   protected void route(List<SendingMailbox> destinations, MseBlock.Data block) 
{
+    // Serialized blocks are read-only (every receiver deserializes its own 
copy of the data), so they are always
+    // safe to share
+    if (destinations.size() == 1 || !block.isRowHeap() || 
!block.asRowHeap().containsObjectColumns()) {
+      for (SendingMailbox mailbox : destinations) {
+        sendBlock(mailbox, block);
+      }
+      return;
+    }
+    // Send a copy to every active local destination except the first one, 
which receives the original block without
+    // copying. Remote destinations serialize the original block on this 
thread, and the copies are also made on this
+    // thread, so all reads of the original block finish before it is handed 
to a local receiver that can start
+    // mutating it.
+    RowHeapDataBlock rowHeapBlock = block.asRowHeap();
+    SendingMailbox firstLocalDestination = null;
     for (SendingMailbox mailbox : destinations) {
-      sendBlock(mailbox, block);
+      if (mailbox.isEarlyTerminated()) {
+        continue;
+      }
+      if (!mailbox.isLocal()) {

Review Comment:
   Non-blocking, but worth a follow-up: this optimisation doesn't apply to the 
case the PR is actually fixing, so spools still pay serde round trips for 
objects that are only going to be serialized anyway.
   
   `BlockExchange.BlockExchangeSendingMailbox#isLocal()` returns an 
unconditional `true`, regardless of whether the inner exchange's own mailboxes 
are local or `GrpcSendingMailbox`. Every outer destination of a multi-send node 
is one of these, so the `!mailbox.isLocal()` branch is never taken for a spool: 
a spool whose receiver stages all live on other servers still gets 
`copyObjectColumns()` for each extra stage, even though every one of those 
inner exchanges will immediately serialize the block on this same thread and 
never hand it to a mutating receiver. That's a full serialize + deserialize per 
intermediate, per extra stage, purely to throw the copy away at the next hop.
   
   Underneath it, `isLocal()` now carries two different meanings:
   - the original one, used by `BlockExchange#sendBlock`: *don't split here, 
pass through*;
   - the new one, used here: *may hand the block to a receiver by reference*.
   
   For `BlockExchangeSendingMailbox` those disagree, and only the conservative 
direction saves us. A separate predicate on `SendingMailbox` — something like 
`deliversByReference()`, which `BlockExchangeSendingMailbox` answers by 
delegating to its inner mailboxes — would let the remote case skip the copy and 
keep `isLocal()` meaning only what `sendBlock` needs.
   
   Related, and also fine as a follow-up: the contract the optimisation relies 
on (`send(MseBlock.Data)` serializes synchronously on the calling thread) is 
documented on `GrpcSendingMailbox`, but `route` depends on it for *any* mailbox 
reporting `!isLocal()`, present or future. It would be better stated on 
`SendingMailbox#send` so it reads as a requirement on implementers.



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