xiangfu0 commented on code in PR #20021:
URL: https://github.com/apache/pinot/pull/20021#discussion_r4451374703
##########
pinot-broker/src/main/java/org/apache/pinot/broker/routing/manager/BaseBrokerRoutingManager.java:
##########
@@ -461,19 +501,22 @@ private void processInstanceConfigChangeInternal() {
// hit the identity fast path
instanceId = serverInstance.getInstanceId();
enabledServers.add(instanceId);
- if (_enabledServerInstanceMap.put(instanceId, serverInstance) ==
null) {
+ ServerInstance previousInstance =
_enabledServerInstanceMap.put(instanceId, serverInstance);
+ if (previousInstance == null) {
newEnabledServers.add(instanceId);
// NOTE: Remove new enabled server from excluded servers because
the server is likely being restarted
if (_excludedServers.remove(instanceId)) {
LOGGER.info("Got excluded server: {} re-enabled, including it
into the routing", instanceId);
}
- // The server likely restarted, possibly at a new address. The
callback refreshes the GRPC channel to it:
- // it clears any reconnection backoff, without which MSE queries
to this server may fail fast until the
- // next backoff retry succeeds, and it makes the channel resolve
the hostname again.
- if (_serverEnabledCallback != null) {
- _serverEnabledCallback.accept(serverInstance);
- }
+ notifyIfAddressChanged(_disabledServers.remove(instanceId),
serverInstance);
+ // The server likely restarted, possibly at a new address. The
callbacks drop what may be tied to its
+ // previous process: the GRPC channel's reconnection backoff,
without which MSE queries to this server may
+ // fail fast until the next backoff retry succeeds, and its stale
resolved address; cached Netty channels
+ // that may be half-open.
+ notifyServerEnabled(serverInstance);
Review Comment:
Done in fcd376aa31, along the lines you suggested. The routing manager now
also tells the handler when an enabled server gets disabled
(`addServerDisabledCallback`), and the broker notes the disable on that
server's cached channels. On re-enable, a channel is kept if it received data
after the disable and still has queries waiting on it, so a toggle leaves those
queries to finish.
A restart doesn't take that path. A server sets the shutdown flag as it
starts up, even after a crash, so the channel to the old process hears nothing
after the disable and gets evicted. After a graceful shutdown that channel is
already closed by the time the server comes back. I added the "still has
queries waiting" condition so that a channel which answered during a graceful
drain and then went half-open (the host dying mid-shutdown) isn't kept for
nothing. Evicting an idle channel only costs a reconnect.
Tests:
`ServerChannelsTest#testEnabledServerKeepsOnlyChannelHeardFromSinceDisabledThatCarriesQueries`,
`QueryRoutingTest#testEnabledServerKeepsChannelThatCarriesQueriesAndAnsweredSinceDisabled`
against a real server, and
`BrokerRoutingManagerTest#testDisabledCallbackInvokedWhenEnabledServerIsDisabled`.
##########
pinot-core/src/main/java/org/apache/pinot/core/transport/ServerChannels.java:
##########
@@ -365,11 +496,31 @@ private void awaitTlsHandshake(Channel channel, long
timeoutMs)
}
}
- void sendRequestWithoutLocking(String rawTableName, AsyncQueryResponse
asyncQueryResponse,
+ /// Returns the channel the request was written to.
+ Channel sendRequestWithoutLocking(String rawTableName, AsyncQueryResponse
asyncQueryResponse,
ServerRoutingInstance serverRoutingInstance, byte[] requestBytes) {
- long startTimeMs = System.currentTimeMillis();
- // The listener runs later, by when a reconnect may have replaced
_channel: on failure it closes this connection.
+ // Pin the channel: the completion listener must act on the channel it
wrote to, not on whatever channel this
+ // entry holds by the time the write completes, or a late failure would
close a healthy reconnected channel.
Channel channel = _channel;
+ if (!channel.isWritable()) {
Review Comment:
I took the first option in fcd376aa31. The rejection now throws its own
`UnwritableChannelException`, and `QueryRouter` fails the query (or skips the
server) without recording the server as the failed one, so the failure detector
never hears about it. A real stall is still judged by the write timeout, which
closes the channel, and that close reports the server as before.
Pings now skip the check and queue behind the full queue instead. They're
tiny, and whether one gets answered is exactly what tells a healthy server
draining a big request from a stalled one, so rejecting it would fail it either
way.
I didn't add a grace wait: it would hold the channel lock and the query
thread while the window is closed, which is what the rejection is there to
avoid. The watermark is configurable if 16 MiB is too low for a deployment with
very large requests. #20022 also no longer marks the channel suspect on a
rejection, for the same reason.
Tests: `QueryRoutingTest#testUnwritableRejectionDoesNotReportServer` (with
and without `skipUnavailableServers`) and
`ServerChannelsTest#testPingIsQueuedOnUnwritableChannel`.
##########
pinot-core/src/main/java/org/apache/pinot/core/transport/AsyncQueryResponse.java:
##########
@@ -237,8 +263,27 @@ void cancelQuery(ServerRoutingInstance
serverRoutingInstance, Exception exceptio
/// Returns `true` if a server-down event should be acted on: the server was
queried by this response and has not
/// responded yet
private boolean shouldActOnServerDown(ServerRoutingInstance
serverRoutingInstance) {
+ return shouldActOnServerDown(serverRoutingInstance, null, true);
+ }
+
+ /// As above, and when `channel` is given the request must have been written
to that channel. A request whose
+ /// channel is not known yet (it is about to be written, possibly to a
channel that is still being connected) is
+ /// still taken down by a genuine failure of the server, as before, but
never by a close the broker itself made:
+ /// that close only concerns the queries demonstrably written to the closed
channel.
+ private boolean shouldActOnServerDown(ServerRoutingInstance
serverRoutingInstance, @Nullable Channel channel,
+ boolean reportServer) {
ServerResponse serverResponse = _responseMap.get(serverRoutingInstance);
- return serverResponse != null && serverResponse.getDataTable() == null;
+ if (serverResponse == null || serverResponse.getDataTable() != null) {
+ return false;
+ }
+ if (channel == null) {
+ return true;
+ }
+ Channel requestChannel = serverResponse.getChannel();
+ if (requestChannel == null) {
+ return reportServer;
Review Comment:
Agreed, fixed in fcd376aa31. When a channel is given, only requests pinned
to that channel are affected now. An unpinned request hasn't been written yet,
and its own send settles it: either it gets pinned to a live channel, after
which that channel's events apply, or its write or connect fails and fails it.
The variant without a channel, used for broker-side shedding, still covers the
whole server. I updated
`AsyncQueryResponseTest#testMarkServerUnavailableIsScopedToTheRequestChannel`
and `#testBrokerInitiatedCloseDoesNotBlameTheServer` to match.
##########
pinot-common/src/main/java/org/apache/pinot/common/metrics/BrokerMeter.java:
##########
@@ -232,6 +232,20 @@ public class BrokerMeter implements AbstractMetrics.Meter {
"NETTY_CONNECTION_CHANNEL_ACTIVE", "nettyConnection", false);
public static final BrokerMeter NETTY_CONNECTION_CHANNEL_INACTIVE = create(
"NETTY_CONNECTION_CHANNEL_INACTIVE", "nettyConnection", false);
+ // Requests rejected because the channel's outbound queue was above its high
watermark, i.e. the send window to the
+ // server is closed (see NettyConfig.WRITE_BUFFER_HIGH_WATER_MARK_BYTES).
Each one is a query failed fast instead of
+ // being queued in direct memory behind a stalled socket.
+ public static final BrokerMeter NETTY_CONNECTION_UNWRITABLE_REJECTIONS =
create(
Review Comment:
Done in fcd376aa31. `NETTY_CONNECTION_UNWRITABLE_REJECTIONS`,
`NETTY_CONNECTION_WRITE_TIMEOUTS` and `NETTY_CONNECTION_CHANNEL_EVICTIONS` are
now tagged with the server's short name through `addMeteredValue`, the same way
as `NETTY_CONNECTION_CHANNEL_INACTIVE`. They're new in this PR, so no existing
metric changes name.
--
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]