Skip to content

HDDS-16737. Share OM follower read logic between the Hadoop RPC and gRPC clients - #11418

Open
peterxcli wants to merge 1 commit into
apache:masterfrom
peterxcli:HDDS-16737
Open

peterxcli wants to merge 1 commit into
apache:masterfrom
peterxcli:HDDS-16737

Conversation

@peterxcli

Copy link
Copy Markdown
Member

What changes were proposed in this pull request?

The Hadoop RPC client and the gRPC client (used by the S3 Gateway) each have their own copy of the OM follower read logic. The Hadoop RPC one lives in HadoopRpcOMFollowerReadFailoverProxyProvider, while the gRPC one was added straight into GrpcOmTransport by HDDS-15492. So every follower read change, like the per-request read consistency in HDDS-15089, has to be made twice.

As suggested in #11252 (comment), this PR moves the shared parts into a base class:

  • FollowerReadFailoverProxyProviderBase keeps the follower read settings, decides whether a request may go to a follower, adds the default read consistency hint, and picks the OM node for follower reads (skipping the leader for LOCAL_LEASE).
  • HadoopRpcOMFollowerReadFailoverProxyProvider now extends it.
  • The gRPC follower read logic moves out of GrpcOmTransport into a new GrpcOMFollowerReadFailoverProxyProvider, which also extends it. GrpcOmTransport is left with just sending the requests.

The behavior stays the same, with one small difference. The Hadoop RPC provider now calls createOMProxyIfNeeded every time it picks a follower node, not only when it moves to the next node. So if a DNS refresh has dropped the cached proxy of the current node, the proxy is created again, instead of the request hitting an NPE on that node and moving on to the next one.

What is the link to the Apache JIRA

https://issues.apache.org/jira/browse/HDDS-16737

How was this patch tested?

  • New unit tests for the two new classes: TestFollowerReadFailoverProxyProviderBase and TestGrpcOMFollowerReadFailoverProxyProvider.
  • The existing follower read tests pass without changes: TestHadoopRpcOMFollowerReadFailoverProxyProvider, TestS3GrpcOmTransport, TestOzoneManagerHAFollowerReadWithAllRunning and TestOzoneManagerHAFollowerReadWithStoppedNodes.

Copilot AI balanced review requested due to automatic review settings October 6, 2026 09:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@peterxcli

Copy link
Copy Markdown
Member Author

most of the addition are tests

@peterxcli
peterxcli requested a review from ivandika3 October 6, 2026 12:05
@peterxcli
peterxcli marked this pull request as ready for review October 6, 2026 12:05
@ivandika3

ivandika3 commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

@echonesis Please help take a look as well since this concerns gRPC client.

@ivandika3 ivandika3 left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks @peterxcli for the patch, left some comments. There are some behavior change concerns.

*
* @param nodeId the expected current node.
*/
protected synchronized void changeFollowerReadNodeId(String nodeId) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: Can annotate nodeId as @Nonnull and can probably add Objects.requireNonNull.

Comment on lines +147 to +162
protected synchronized String selectFollowerReadNodeId(ReadConsistency readConsistency) {
String nodeId = getCurrentFollowerReadNodeId();
if (readConsistency != ReadConsistency.LOCAL_LEASE) {
return nodeId;
}

final String leaderNodeId = getLeaderProxy().getCurrentProxyOMNodeId();
for (int i = 0; i < getOMNodeCount(); i++) {
if (!nodeId.equals(leaderNodeId)) {
return nodeId;
}
changeFollowerReadNodeId(nodeId);
nodeId = getCurrentFollowerReadNodeId();
}
return null;
}

@ivandika3 ivandika3 Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In the previous logic, we don't necessary require client to send to a follower first (for perf reason since currently things linearizable read latency is significantly higher than leader read), but seems this patch changes the logic prioritize follower first.

I suggest we address behavior change in another ticket (and probably gate it in a configuration with name similar to follower affinity in OmMetadataGenerator).

* The index of the OM node used for follower read, in the order of the leader proxy's OM nodes.
* Should only be accessed in synchronized methods.
*/
private int currentIndex = 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Before the refactor, HadoopRpcOMFollowerReadFailoverProxyProvider initialized currentIndex to -1, IIRC so that it changeProxy will trigger creation for index 0. If we set this to 0, will this skip the first OM proxy in changeFollowerReadNodeId?

@echonesis echonesis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks @peterxcli for the refactor.
Just leave some comments.

Comment on lines +164 to +165
public synchronized void changeInitialProxyForTest(String initialOmNodeId) {
currentIndex = getLeaderProxy().getOMProxyMap().indexOf(initialOmNodeId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could we retain the null check around indexOf(initialOmNodeId)?
An unknown node ID left the current selection unchanged. With this assignment, the returned Integer would be unboxed and throw an NPE.

Comment on lines +205 to 213
for (int i = 0; i < getOMNodeCount(); i++) {
final String nodeId = selectFollowerReadNodeId(readConsistency);
if (nodeId == null) {
break;
}
final OMProxyInfo<OzoneManagerProtocolPB> current = leaderProxy.createOMProxyIfNeeded(nodeId);
LOG.debug("Attempting to service submitRequest with cmdType {} using proxy {}",
omRequest.getCmdType(), current.proxyInfo);
try {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would it be possible to add a regression test for that case?
It would be useful to verify that, after DNS refresh invalidates the current node’s cached proxy, the next read recreates it using the refreshed address and succeeds on the same node without moving to another OM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants