Skip to content

transport: log network reconnects with same peer process #128415

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

schase-es
Copy link
Contributor

ClusterConnectionManager now caches the previous ephemeralId (created on process-start) of peer nodes on disconnect in a connection history table. On reconnect, when a peer has the same ephemeralId as it did previously, this is logged to indicate a network failure. The connectionHistory is trimmed to the current set of peers by NodeConnectionsService.

ClusterConnectionManager now caches the previous ephemeralId (created on
process-start) of peer nodes on disconnect in a connection history table. On
reconnect, when a peer has the same ephemeralId as it did previously, this is
logged to indicate a network failure. The connectionHistory is trimmed to the
current set of peers by NodeConnectionsService.
@schase-es schase-es added >non-issue :Distributed Coordination/Network Http and internode communication implementations labels May 24, 2025
@elasticsearchmachine elasticsearchmachine added the Team:Distributed Coordination Meta label for Distributed Coordination team label May 24, 2025
@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/es-distributed-coordination (Team:Distributed Coordination)

@schase-es
Copy link
Contributor Author

I wasn't able to find a way to test the ClusterConnectionManager's connectionHistory table when integrated through the NodeConnectionsService.

Copy link
Contributor

@nicktindall nicktindall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good, just a few questions and minor comments.

/**
* Keep the connection history for the nodes listed
*/
void retainConnectionHistory(List<DiscoveryNode> nodes);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the javadoc I think we should mention that we discard history for nodes not in the list? If you know the Set API then it's suggested by the name retain, but if you don't it might not be obvious.

@@ -120,6 +122,7 @@ public void connectToNodes(DiscoveryNodes discoveryNodes, Runnable onCompletion)
runnables.add(connectionTarget.connect(null));
}
}
transportService.retainConnectionHistory(nodes);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might be able to use DiscoveryNodes#getAllNodes() rather than building up an auxiliary collection, that might be marginally more efficient? Set#retainAll seems to take a Collection, but we'd need to change the ConnectionManager#retainConnectionHistory interface to accommodate.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a separate collection here at all? We could just pass discoveryNodes around I think.

But also, really this is cleaning out the nodes about which we no longer care, so I think we should be doing this in disconnectFromNodesExcept instead.

public void onFailure(Exception e) {
final NodeConnectionHistory hist = new NodeConnectionHistory(node.getEphemeralId(), e);
nodeHistory.put(conn.getNode().getId(), hist);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to store the connection history even when conn.hasReferences() == false ? I'm not 100% familiar with this code, but I wonder if we might get the occasional ungraceful disconnect after we've released all our references?

I guess in that case we would eventually discard the entry via retainConnectionHistory anyway.

Do we need to be careful with the timing of calls to retainConnectionHistory versus the these close handlers firing? I guess any entries that are added after a purge would not survive subsequent purges.

node.descriptionWithoutAttributes(),
e,
ReferenceDocs.NETWORK_DISCONNECT_TROUBLESHOOTING
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like previously we would only have logged at debug level in this scenario? unless I'm reading it wrong. I'm not sure how interesting this case is (as we were disconnecting from the node anyway)?

assertTrue("recent disconnects should be listed", connectionManager.connectionHistorySize() == 2);

connectionManager.retainConnectionHistory(Collections.emptyList());
assertTrue("connection history should be emptied", connectionManager.connectionHistorySize() == 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it would be better to expose a read-only copy of the map for testing this, that would allow us to assert that the correct IDs were present?

Copy link
Contributor

@DaveCTurner DaveCTurner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ClusterConnectionManager isn't quite the right place to do this - the job of this connection manager is to look after all node-to-node connections including ones used for discovery and remote cluster connections too. There are situations where we might close and re-establish these kinds of connection without either end restarting without that being a problem worthy of logging.

NodeConnectionsService is the class that knows about connections to nodes in the cluster. I'd rather we implemented the logging about unexpected reconnects there. That does raise some difficulties about how to expose the exception that closed the connection, if such an exception exists. I did say that this bit would be tricky 😁 Nonetheless I'd rather we got the logging to happen in the right place first and then we can think about the plumbing needed to achieve this extra detail.

value = "org.elasticsearch.transport.ClusterConnectionManager:WARN",
reason = "to ensure we log cluster manager disconnect events on WARN level"
)
public void testExceptionalDisconnectLoggingInClusterConnectionManager() throws Exception {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we put this into its own test suite? This suite is supposed to be about ESLoggingHandler which is unrelated to the logging in ClusterConnectionManager. I think this test should work fine in the :server test suite, no need to hide it in the transport-netty4 module.

Also could you open a separate PR to move testConnectionLogging and testExceptionalDisconnectLogging out of this test suite - they're testing the logging in TcpTransport which is similarly unrelated to ESLoggingHandler. IIRC they were added here for historical reasons, but these days we use the Netty transport everywhere so these should work in :server too.

@@ -120,6 +122,7 @@ public void connectToNodes(DiscoveryNodes discoveryNodes, Runnable onCompletion)
runnables.add(connectionTarget.connect(null));
}
}
transportService.retainConnectionHistory(nodes);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a separate collection here at all? We could just pass discoveryNodes around I think.

But also, really this is cleaning out the nodes about which we no longer care, so I think we should be doing this in disconnectFromNodesExcept instead.

Comment on lines +241 to +242
NodeConnectionHistory hist = nodeHistory.remove(connNode.getId());
if (hist != null && hist.ephemeralId.equals(connNode.getEphemeralId())) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we extract this to a separate method rather than adding to this already over-long and over-nested code directly?

Also I'd rather use nodeConnectionHistory instead of hist. Abbreviated variable names are a hindrance to readers, particularly if they don't have English as a first language, and there's no disadvantage to using the full type name here.

(nit: also it can be final)

if (hist.disconnectCause != null) {
logger.warn(
() -> format(
"transport connection reopened to node with same ephemeralId [%s], close exception:",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Users don't really know what ephemeralId is so I think will find this message confusing. Could we say something like reopened transport connection to node [%s] which disconnected exceptionally [%s/%dms] ago but did not restart, so the disconnection is unexpected? NB also tracking the disconnection duration here.

Similarly disconnected gracefully in the other branch.

Also can we link ReferenceDocs.NETWORK_DISCONNECT_TROUBLESHOOTING?

// that's a bug.
} else {
logger.debug("closing unused transport connection to [{}]", node);
conn.addCloseListener(new ActionListener<Void>() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: reduce duplication a bit here:

                                conn.addCloseListener(new ActionListener<>() {
                                    @Override
                                    public void onResponse(Void ignored) {
                                        addNewNodeConnectionHistory(null);
                                    }

                                    @Override
                                    public void onFailure(Exception e) {
                                        addNewNodeConnectionHistory(e);
                                    }

                                    private void addNewNodeConnectionHistory(@Nullable Exception e) {
                                        nodeHistory.put(node.getId(), new NodeConnectionHistory(node.getEphemeralId(), e));
                                    }
                                });

Also consider extracting this out to the top level to try and keep this method length/nesting depth from getting too much further out of hand.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
:Distributed Coordination/Network Http and internode communication implementations >non-issue Team:Distributed Coordination Meta label for Distributed Coordination team v9.1.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants