[client] Recover Admin writes after coordinator failover - #4200
[client] Recover Admin writes after coordinator failover#4200sakshichitnis27 wants to merge 2 commits into
Conversation
| // Retrying generic network errors is unsafe for non-idempotent writes because the request | ||
| // may already have succeeded. NotCoordinatorLeaderException is safe because the standby | ||
| // rejects the request before invoking the coordinator API. | ||
| this.gateway = |
There was a problem hiding this comment.
I have two suggestions:
-
I previously implemented this in [PR #3390]([client] Fix stale metadata on readOnlyGateway by adding RetryableGatewayClientProxy #3390), but a reviewer reminded me that write operations are not idempotent, so we should not retry them automatically. I’m thinking that we could still return an error without retrying, but refresh the metadata before doing so. This way, the operation can recover the next time the user retries it manually.
-
With the approach described in point 1, we should not limit metadata refresh to cases where the RPC response contains a
NotCoordinatorLeaderException. During an upgrade, the old CoordinatorServer’s IP address is not necessarily reused by a TabletServer. If there are spare IP addresses, the old IP may remain unused, in which case the request may fail with aNetworkExceptioninstead.
@litiliu , WDYT?
There was a problem hiding this comment.
Thanks @loserwang1024
On point 2 (don't limit refresh to NotCoordinatorLeaderException): agreed. After a failover the old coordinator may be gone or its IP not reused, so the write can fail with NetworkException/TimeoutException instead. We should refresh cluster metadata (and drop the stale coordinator connection) on any failure so the client can recover.
On point 1 (writes are non-idempotent, don't auto-retry): agreed in general, but NotCoordinatorLeaderException is a special, safe case. In FlussRequestHandler#processRequest the leader check runs before the write method is invoked:
if (isCoordinator && api.getApiKey() != ApiKeys.API_VERSIONS) {
if (!((CoordinatorGateway) service).isLeader()) {
request.fail(
new NotCoordinatorLeaderException(
"This coordinator server is not the current leader."));
return;
}
}
So this exception guarantees the mutation was rejected before execution — retrying it cannot duplicate a write. NetworkException/TimeoutException may already have executed (lost response), so those must NOT be auto-retried.
Proposed policy for the write gateway:
On any failure → refresh metadata + discard the stale coordinator connection (recovers the NetworkException/upgrade case; the user's next manual retry then succeeds).
Auto-retry once only for NotCoordinatorLeaderException (provably safe; better UX for the standby-alive case).
This keeps auto-retry strictly to the provably-safe error while still refreshing metadata for everything else. WDYT?
|
Thanks @sakshichitnis27 for driving this, and @loserwang1024 for the review. Based on that feedback I put up an alternative implementation in #4216 for #4027, in case it's useful to the discussion. Key differences that address the two review points:
Happy to fold whichever direction the community prefers into a single PR — didn't mean to fragment the effort. |
|
Thanks @loserwang1024 and @litiliu. I’ve updated the PR to separate metadata refresh from automatic retry:
|
Purpose
Linked issue: close #4027
A long-lived Java Admin client keeps using its cached coordinator connection after leadership moves to a standby. Coordinator write operations then continue to fail with NotCoordinatorLeaderException until the connection is recreated.
Brief change log
Tests
API and Format
No public API or storage format changes. The new retry-predicate overload is internal.
Documentation
No documentation changes. This fixes existing Admin failover behavior.