xiangfu0 commented on code in PR #19875:
URL: https://github.com/apache/pinot/pull/19875#discussion_r4278302292


##########
pinot-server/src/main/java/org/apache/pinot/server/starter/helix/ServerMailboxChannelRefreshHandler.java:
##########
@@ -145,24 +167,51 @@ private static boolean 
isShutdownInProgress(InstanceConfig config) {
         
config.getRecord().getStringField(CommonConstants.Helix.IS_SHUTDOWN_IN_PROGRESS,
 "false"));
   }
 
+  /// Records the server's current mailbox address, and retires the channel to 
its previous one if it moved.
+  private void updateMailboxAddress(String instanceId, InstanceConfig config) {
+    Pair<String, Integer> address = getMailboxAddress(instanceId, config);
+    Pair<String, Integer> previousAddress =
+        address != null ? _mailboxAddresses.put(instanceId, address) : 
_mailboxAddresses.remove(instanceId);
+    if (previousAddress != null && !previousAddress.equals(address)) {
+      retireChannel(instanceId, previousAddress);
+    }
+  }
 
-  private void resetBackoffForServer(String instanceId, InstanceConfig config) 
{
+  private void retireChannel(String instanceId, Pair<String, Integer> address) 
{
+    // Another server may have taken over the address
+    if (_mailboxAddresses.containsValue(address)) {

Review Comment:
   Same O(N) `containsValue` scan as on the dispatcher side — runs once per 
server that comes back on a new mailbox port during a rolling restart. It's 
negligible for typical clusters and the ordering here is correct, so no change 
needed; just flagging it in case very large clusters with mass port changes 
ever show up in a profile. A reverse address->count multiset would make it O(1) 
if it ever matters.



##########
pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/channel/ChannelManager.java:
##########
@@ -150,18 +156,63 @@ private ManagedChannel watchState(ManagedChannel channel, 
String hostname, int p
     return channel;
   }
 
-  /// Resets the connection backoff for the channel to the given server if the 
channel is in
-  /// TRANSIENT_FAILURE state. Returns true if a reset was performed, false 
otherwise.
+  /// Makes the channel to the given address resolve the hostname again and 
open a new connection before its next
+  /// stream, dropping the connection it has, and any reconnect backoff. 
Streams in flight finish on the old
+  /// connection. Returns false if there is no channel to the address, or if 
it was refreshed less than a second ago.
+  ///
+  /// A channel resolves the hostname only when it starts, and again after a 
connection fails or is lost. When the
+  /// server at that address goes away, gRPC resolves once, right then, and 
keeps that answer until the next failure.
+  /// If DNS still had the old IP address at that moment (a terminating pod 
stays in DNS for a while), the channel
+  /// later reconnects to the old address, and when another server took that 
address over and listens on the same
+  /// port, the connection succeeds and is never questioned again. Call this 
when the address can be stale, for example
+  /// when the server at the address has restarted.
   ///
-  /// @return true if the channel was in TRANSIENT_FAILURE and backoff was 
reset
-  public boolean resetConnectBackoff(String hostname, int port) {
-    ManagedChannel channel = _channelMap.get(Pair.of(hostname, port));
-    if (channel != null && channel.getState(false) == 
ConnectivityState.TRANSIENT_FAILURE) {
-      LOGGER.info("Resetting mailbox channel backoff for server: {}:{}", 
hostname, port);
-      channel.resetConnectBackoff();
-      return true;
+  /// Moving the channel to IDLE, rather than replacing it, keeps the channel 
object that concurrent senders may
+  /// already hold valid. The refreshes are limited to one a second, because 
each refresh drops the connection that the
+  /// previous one opened.
+  public boolean refreshChannel(String hostname, int port) {
+    Pair<String, Integer> key = Pair.of(hostname, port);
+    ManagedChannel channel = _channelMap.get(key);
+    if (channel == null) {
+      return false;
+    }
+    long nowNs = System.nanoTime();
+    AtomicLong lastRefreshTimeNs =
+        _lastRefreshTimeNs.computeIfAbsent(key, k -> new AtomicLong(nowNs - 
MIN_REFRESH_INTERVAL_NS));
+    long lastNs = lastRefreshTimeNs.get();
+    if (nowNs - lastNs < MIN_REFRESH_INTERVAL_NS || 
!lastRefreshTimeNs.compareAndSet(lastNs, nowNs)) {
+      return false;
+    }
+    channel.enterIdle();

Review Comment:
   Small caveat worth a Javadoc line: `enterIdle()` only moves the channel to 
IDLE (and so re-resolves on the next RPC) once there are no active RPCs/streams 
on it. For the mailbox channel a long-lived in-flight stream will defer the 
re-resolution until it drains, so the "before the next stream" wording can be 
slightly optimistic. Not a correctness problem — it matches the "streams in 
flight finish on the old connection" behavior — just worth saying explicitly.



##########
pinot-query-runtime/src/main/java/org/apache/pinot/query/service/dispatch/QueryDispatcher.java:
##########
@@ -896,16 +899,31 @@ DispatchClient 
getOrCreateDispatchClient(QueryServerInstance queryServerInstance
             _dispatchMaxInboundMessageSizeBytes));
   }
 
-  /// Reset the connection backoff for a server. When the GRPC channel enters 
a TRANSIENT_FAILURE state from
-  /// connection failures, it will fast fail requests and reconnect with 
exponential backoff. This method
-  /// resets the backoff so servers that have recovered can be reconnected to 
immediately.
-  public void resetClientConnectionBackoff(ServerInstance serverInstance) {
-    String hostname = serverInstance.getHostname();
-    int port = serverInstance.getQueryServicePort();
-    DispatchClient dispatchClient = 
_dispatchClientMap.get(toHostnamePortKey(hostname, port));
+  /// Called when a server becomes routable, which it does when it starts and 
when it is enabled again after it was
+  /// disabled or excluded. The server may have restarted at a new address, so:
+  ///
+  /// - the channel to its address resolves the hostname again before the next 
request, and connects without
+  ///   reconnect backoff. A channel resolves only when a connection fails or 
is lost, and the last time was likely
+  ///   when the server went away, while DNS could still have the old IP 
address. Reconnecting to that address could
+  ///   reach another server that took it over.
+  /// - the channel to its previous address, if the server moved to another 
port, is shut down, since nothing uses it
+  ///   again.
+  public void onServerEnabled(ServerInstance serverInstance) {
+    String hostnamePortKey = toHostnamePortKey(serverInstance.getHostname(), 
serverInstance.getQueryServicePort());
+    String previousKey = 
_dispatchAddressByInstanceId.put(serverInstance.getInstanceId(), 
hostnamePortKey);
+    if (previousKey != null && !previousKey.equals(hostnamePortKey)
+        && !_dispatchAddressByInstanceId.containsValue(previousKey)) {

Review Comment:
   This retire guard — "don't shut down the old channel if another server 
already took the address over" — is logic-bearing but untested on the 
dispatcher side. `QueryDispatcherTest` only covers the move-to-a-fresh-port 
case (`port` -> `port+1`), where `containsValue` is always false. Worth adding 
a test: enable A at addr1, enable B at addr1, then move A to addr2, and assert 
B's client to addr1 is left alone. (Minor aside: `containsValue` is an O(N) 
scan here; fine at normal cluster sizes, just noting it runs once per 
port-changing re-enable during a rolling restart.)



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