[coordinator] Withhold leader from UpdateMetadataRequest until NotifyLeaderAndIsr is acked - #4150
Open
damokelis wants to merge 1 commit into
Conversation
…s acked The coordinator maintains two independently-updated views of replica state: TabletServerMetadataCache (pushed via UpdateMetadataRequest, determines what leader info clients see) and ReplicaManager.allReplicas (activated via NotifyLeaderAndIsrRequest, determines whether a tablet server can actually serve fetchLog/putKv for a bucket). These are sent as two separate RPCs with no atomicity guarantee between them. Under normal conditions the window between "coordinator decided who the leader is" and "the target tablet server has admitted the replica" is milliseconds and rarely observable. For large tables, however, a restarting tablet server can take minutes to complete local log recovery before it processes NotifyLeaderAndIsrRequest, while UpdateMetadataRequest can be broadcast well before that. A client that picks up the new leader from metadata during this window sends requests to a server whose ReplicaManager still treats the bucket as NoneReplica, and is rejected with NotLeaderOrFollowerException / LeaderNotAvailableException in a tight retry loop until the server finally catches up (observed: single-digit minutes to over ten minutes for a large 12-bucket table). This wires the existing (previously read-only, health-API-only) pendingLeaderActivationBuckets tracking into addUpdateMetadataRequestForTabletServers: a bucket's leader is withheld from UpdateMetadataRequest for as long as it is marked pending, and the mark is only cleared once the corresponding NotifyLeaderAndIsrRequest is acked by the target server (or the replica goes offline via re-election). sendNotifyLeaderAndIsrRequest already runs strictly before sendUpdateMetadataRequest in the same batch, so the pending mark set for an activation round is always visible to the accompanying metadata push. One subtlety worth calling out: an earlier iteration of this change also cleared the pending mark when the NotifyLeaderAndIsrRequest RPC itself failed to send (e.g. target server not yet reachable during a rolling restart), reasoning that this avoided a stale RED in the health API. That turned out to reintroduce the exact race being fixed here, just triggered by RPC failure instead of by slow log recovery: clearing the mark let the leader be advertised to clients while the target server's ReplicaManager had never actually admitted the replica. The fix keeps the mark untouched on send failure; it is only cleared by a real ack, or by onReplicaBecomeOffline during heartbeat-timeout-triggered re-election (this offline-cleanup path is also new here, since a bucket whose NotifyLeaderAndIsrRequest comes back with a per-bucket error never reaches the ack path and would otherwise be stuck pending forever). Also fixes a plain Map.put overwrite in addUpdateMetadataRequestForTabletServers (case3/4/10/11) that silently discards already-queued non-empty bucket data for the same table within one UpdateMetadataRequest batch, by logging when it happens instead of leaving it silent. Tested: mvn -pl fluss-server -am test (org.apache.fluss.server.coordinator.**), 352/353 passing; the sole unrelated failure (CoordinatorServerITCase#testRunServerUsingProcess) reproduces identically on an unmodified checkout and is a local environment issue with spawning a separate JVM process, not a regression from this change. Reproduced and verified against a real cluster: rolling-restarted three tablet servers hosting a large table (including one requiring ~11 minutes of local log recovery), with a tiering client continuously reading from the affected buckets throughout. Before this fix, NotLeaderOrFollowerException/LeaderNotAvailableException recurred for minutes after all tablet servers reported Ready. After this fix, the client's cumulative error count did not increase at all across the full rolling-restart window plus an additional 10-minute observation period.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
Linked issue: close #4149
The coordinator maintains two independently-updated views of replica state:
TabletServerMetadataCache(pushed viaUpdateMetadataRequest, determines what leader info clients see) andReplicaManager.allReplicas(activated viaNotifyLeaderAndIsrRequest, determines whether a tablet server can actually servefetchLog/putKvfor a bucket). These are sent as two separate RPCs (CoordinatorRequestBatch#sendRequestToTabletServers) with no atomicity guarantee between them.Under normal conditions the window between "coordinator decided who the leader is" and "the target tablet server has admitted the replica" is milliseconds and rarely observable. For large tables, however, a restarting tablet server can take minutes to complete local log recovery before it processes
NotifyLeaderAndIsrRequest, whileUpdateMetadataRequestcan be broadcast well before that (e.g. viaCoordinatorEventProcessor#processNewTabletServerwhen the server re-registers). A client that picks up the new leader from metadata during this window sends requests to a server whoseReplicaManagerstill treats the bucket asNoneReplica, and is rejected withNotLeaderOrFollowerException/LeaderNotAvailableExceptionin a tight retry loop until the server finally catches up (observed: single-digit minutes to over ten minutes for a large 12-bucket table). See #4149 for full details and reproduction steps.Brief change log
CoordinatorContext: exposeisPendingLeaderActivation(TableBucket)on the existing (previously read-only, health-API-only)pendingLeaderActivationBucketstracking.CoordinatorRequestBatch#addUpdateMetadataRequestForTabletServers: withhold a bucket's leader fromUpdateMetadataRequestfor as long asisPendingLeaderActivationis true.sendNotifyLeaderAndIsrRequestalready runs strictly beforesendUpdateMetadataRequestin the same batch, so the pending mark set for an activation round is always visible to the accompanying metadata push.CoordinatorEventProcessor#processNotifyLeaderAndIsrResponseReceivedEvent: when a pending bucket's activation is genuinely acknowledged, clear the mark and proactively push a follow-upUpdateMetadataRequest(rather than waiting for some unrelated coordinator event to trigger the next broadcast). Also clear the mark for buckets that go offline viaonReplicaBecomeOffline, since a bucket whoseNotifyLeaderAndIsrRequestcomes back with a per-bucket error never reaches the ack path and would otherwise be stuck pending forever, permanently withholding its leader even after a successful re-election.CoordinatorRequestBatch#sendNotifyLeaderAndIsrRequest: on RPC send failure, deliberately leave the pending mark untouched (see "Notable subtlety" below).Map.putoverwrite in the same method (case3/4/10/11) that could silently discard already-queued non-empty bucket data for the same table within oneUpdateMetadataRequestbatch — now logged instead of silent.Notable subtlety: an earlier iteration of this change also cleared the pending mark when the
NotifyLeaderAndIsrRequestRPC itself failed to send (e.g. target server not yet reachable during a rolling restart), reasoning that this avoided a stale RED in the health API. That turned out to reintroduce the exact race being fixed here, just triggered by RPC failure instead of by slow log recovery: clearing the mark let the leader be advertised to clients while the target server'sReplicaManagerhad never actually admitted the replica. The current version keeps the mark untouched on send failure; a transientGetClusterHealthRED during this window is expected and reflects genuine uncertainty, not a bug.Tests
mvn -pl fluss-server -am test -Dtest='org.apache.fluss.server.coordinator.**': 352/353 passing. The sole unrelated failure (CoordinatorServerITCase#testRunServerUsingProcess) reproduces identically on an unmodified checkout and is a local-environment issue spawning a separate JVM process, not a regression from this change.CoordinatorRequestBatchTest#testNotifyLeaderAndIsrSendFailureLeavesLeaderPending(renamed/inverted from a prior version that asserted the opposite, now-incorrect, behavior): asserts the pending mark survives an RPC send failure.CoordinatorRequestBatchTest#testNotifyLeaderAndIsrSendFailureToFollowerDoesNotClearOtherPending: asserts a follower-send failure doesn't touch an unrelated bucket's pending mark.CoordinatorEventProcessorTest(existingtestSchemaChange/testTableRegistrationChangeetc.): exercise the proactive follow-upUpdateMetadataRequestpush after a pending mark is cleared.NotLeaderOrFollowerException/LeaderNotAvailableExceptionrecurred for minutes after all tablet servers reported ready. After this fix, the client's cumulative error count did not increase at all across the full rolling-restart window plus an additional 10-minute observation period.API and Format
No public API or on-disk/wire format change. Behavior change is purely in when a bucket's leader becomes visible via
UpdateMetadataRequest/metadata()— it is delayed until the leader is actually able to serve requests, never advertised earlier than before.Documentation
No new user-facing feature; no documentation change needed.
Generative AI disclosure
Root-cause investigation, code changes, and this PR/issue writeup were produced with Claude Code assistance. All changes were reviewed by a human, verified with the automated test suite, and independently validated against a live cluster (real rolling-restart reproduction described in the Tests section above) before submission.