[ 
https://issues.apache.org/jira/browse/FLINK-40114?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Prashant Bhardwaj updated FLINK-40114:
--------------------------------------
    Description: 
h3. Summary

Since FLINK-36451 made leadership confirmation asynchronous, a component's 
leader information can be *permanently deleted* from the HA backend (e.g. the 
Kubernetes leader ConfigMap) after a leadership loss and re-acquisition.
h3. Root cause

Confirmation runs asynchronously ({{{}confirmLeadershipAsync{}}} on the 
single-threaded {{{}leadershipOperationExecutor{}}}), while the 
leader-information-change handler ({{{}onLeaderInformationChange{}}} → 
{{{}notifyLeaderInformationChangeInternal{}}}) still runs synchronously on the 
driver's watch thread. After a loss + re-acquisition, a component's local 
confirmation is cleared but its stale entry remains in the HA store. If a 
change event is handled before the re-granted session's confirmation lands, the 
handler takes the [empty-confirmation reset 
branch|https://github.com/apache/flink/blob/f9d81b2e11e5cff7469b523cc73ce34b1d28e5ab/flink-runtime/src/main/java/org/apache/flink/runtime/leaderelection/DefaultLeaderElectionService.java#L519]
 and publishes empty, which on Kubernetes [removes the 
key|https://github.com/apache/flink/blob/f9d81b2e11e5cff7469b523cc73ce34b1d28e5ab/flink-kubernetes/src/main/java/org/apache/flink/kubernetes/highavailability/KubernetesLeaderElectionDriver.java#L161].
 Because the self-correcting path runs only once a confirmation exists, if that 
confirmation never lands — due to a transient getConfigMap() failure or session 
churn — the deletion is permanent. Triggered by any API-server disruption 
(outage, rolling upgrade, or intermittent 5xx/429 under load).
h3. Impact

The victim is whichever component's latest-session confirmation fails to land:
 * {{restserver}} erased → REST endpoint cannot be resolved (operator/clients 
cannot reach the cluster).
 * {{{}dispatcher{}}}/{{{}jobmanager{}}} erased → no job submission, no 
TaskManager registration.

The JobManager keeps running and believes it is a healthy leader, so it is 
silent and does not self-recover.
h3. Reproduction

Reproducible deterministically as a unit test by driving 
{{DefaultLeaderElectionService}} with a manually-triggered 
{{leadershipOperationExecutor}} so the confirmation can be held mid-flight: a 
component wins leadership and confirms a session, loses leadership (clearing 
the local confirmation but leaving the stale entry in the store), then wins 
again; while the new session's confirmation is still pending, a 
leader-information-change event carrying the stale entry drops the component's 
key.

  was:
h3. Summary

Since FLINK-36451 made leadership confirmation asynchronous, a race between the 
asynchronous confirmation 
([{{DefaultLeaderElectionService#confirmLeadershipAsync}}|https://github.com/apache/flink/blob/f9d81b2e11e5cff7469b523cc73ce34b1d28e5ab/flink-runtime/src/main/java/org/apache/flink/runtime/leaderelection/DefaultLeaderElectionService.java#L316],
 executed on the single-threaded {{{}leadershipOperationExecutor{}}}) and the 
_synchronous_ leader-information-change handler 
([{{onLeaderInformationChange}}|https://github.com/apache/flink/blob/f9d81b2e11e5cff7469b523cc73ce34b1d28e5ab/flink-runtime/src/main/java/org/apache/flink/runtime/leaderelection/DefaultLeaderElectionService.java#L620],
 executed directly on the driver's watch thread) can cause a component's leader 
information to be *permanently deleted* from the HA backend (e.g. the 
Kubernetes leader ConfigMap) after a leadership loss and re-acquisition.
h3. Root cause

{{confirmLeadershipAsync}} sets {{confirmedLeaderInformation}} and publishes 
the leader entry asynchronously, whereas {{onLeaderInformationChange}} → 
[{{notifyLeaderInformationChangeInternal}}|https://github.com/apache/flink/blob/f9d81b2e11e5cff7469b523cc73ce34b1d28e5ab/flink-runtime/src/main/java/org/apache/flink/runtime/leaderelection/DefaultLeaderElectionService.java#L502]
 runs synchronously under {{lock}} on the watch thread. When a component is 
(re-)granted leadership but its confirmation has not yet populated 
{{{}confirmedLeaderInformation{}}}, and the external store still holds a stale 
entry from a previous session, the change handler takes the 
[{{confirmedLeaderInformation.isEmpty()}} 
branch|https://github.com/apache/flink/blob/f9d81b2e11e5cff7469b523cc73ce34b1d28e5ab/flink-runtime/src/main/java/org/apache/flink/runtime/leaderelection/DefaultLeaderElectionService.java#L519]
 and [publishes 
_empty_|https://github.com/apache/flink/blob/f9d81b2e11e5cff7469b523cc73ce34b1d28e5ab/flink-runtime/src/main/java/org/apache/flink/runtime/leaderelection/DefaultLeaderElectionService.java#L538]
 for that component. On Kubernetes this maps to 
[{{data.remove(key)}}|https://github.com/apache/flink/blob/f9d81b2e11e5cff7469b523cc73ce34b1d28e5ab/flink-kubernetes/src/main/java/org/apache/flink/kubernetes/highavailability/KubernetesLeaderElectionDriver.java#L161]
 in 
[{{KubernetesLeaderElectionDriver}}|https://github.com/apache/flink/blob/f9d81b2e11e5cff7469b523cc73ce34b1d28e5ab/flink-kubernetes/src/main/java/org/apache/flink/kubernetes/highavailability/KubernetesLeaderElectionDriver.java#L126],
 deleting the leader key.

Because the self-correcting ("Correcting"/"Re-writing") paths only run when 
{{confirmedLeaderInformation}} is non-empty, if that component's confirmation 
never lands the deletion is {*}permanent{*}. A confirmation fails to land when:
 * a transient failure is thrown from 
[{{hasLeadershipInternal}}|https://github.com/apache/flink/blob/f9d81b2e11e5cff7469b523cc73ce34b1d28e5ab/flink-runtime/src/main/java/org/apache/flink/runtime/leaderelection/DefaultLeaderElectionService.java#L380]
 → {{hasLeadership()}} → {{getConfigMap()}} — the returned future completes 
exceptionally and is neither retried nor surfaced; or
 * session churn occurs — the confirmation targets a session that is no longer 
the issued one and no-ops.

There are two symmetric windows in which the erasure can occur while we hold 
the lock:
 * {_}confirm-pending{_}: {{issuedLeaderSessionID != null}} but the 
confirmation runnable is still queued or has already failed.
 * {_}grant-pending{_}: the lock is already held (so {{hasLeadership()}} is 
true) but 
[{{onGrantLeadership}}|https://github.com/apache/flink/blob/f9d81b2e11e5cff7469b523cc73ce34b1d28e5ab/flink-runtime/src/main/java/org/apache/flink/runtime/leaderelection/DefaultLeaderElectionService.java#L598]
 has not been processed yet, so {{{}issuedLeaderSessionID == null{}}}. The 
lock-acquire annotation write itself triggers an 
[{{onModified}}|https://github.com/apache/flink/blob/f9d81b2e11e5cff7469b523cc73ce34b1d28e5ab/flink-kubernetes/src/main/java/org/apache/flink/kubernetes/highavailability/KubernetesLeaderElectionDriver.java#L216]
 watch event carrying the stale entries.

h3. Trigger

It is triggered by any API-server disruption that both (a) causes a lease-renew 
miss (leadership loss + re-acquisition) and (b) makes a component's 
confirmation fail to land. Examples:
 * a full API-server outage;
 * a control-plane rolling upgrade;
 * *intermittent 5xx / 429 throttling under load* — a single badly-timed 
{{{}5xx{}}}/{{{}429{}}} on the confirmation's {{getConfigMap()}} GET is enough 
to make it throw (and be swallowed), and repeated renew failures/latency can 
cause the loss + re-acquire.

The stale entry is essentially always present in a loss + re-acquisition: 
revocation clears the local confirmation but leaves the external entry in place 
(and a fresh failover inherits the previous leader's entry). What actually 
varies is (a) whether a confirmation fails to land and (b) whether a watch 
event fires during the unconfirmed window. During a sustained disruption, both 
are near-certain and hit many JobManagers at once. Under merely intermittent 
errors, the two must line up in one brief window, so it is lower-probability 
per re-acquisition but non-zero and routine at fleet scale. The single-threaded 
{{leadershipOperationExecutor}} is an amplifier: slow (even non-failing) 
{{getConfigMap()}} calls stall confirmations and widen the window.
h3. Impact

The victim is whichever component's latest-session confirmation fails to land. 
Consequences by component:
 * {{restserver}} erased → REST endpoint cannot be resolved (operator/clients 
cannot reach the cluster).
 * {{{}dispatcher{}}}/{{{}jobmanager{}}} erased → no job submission, no 
TaskManager registration.

The JobManager keeps running and believes it is a healthy leader, so this is 
silent and doe
h3. Reproduction

The race can be reproduced deterministically as a unit test by driving 
{{DefaultLeaderElectionService}} with a manually-triggered 
{{{}leadershipOperationExecutor{}}}, so the async confirmation can be held 
mid-flight. The scenario mirrors an outage recovery: a component wins 
leadership and confirms a session, clears the local confirmation but leaves the 
now-stale entry in the HA store), then wins again. While the new session's 
confirmation is still pending, a leader-information-change event arrives 
carrying the stale entry, and the component's key is dropped from the store. 
The added tests in {{DefaultLeaderElectionServiceTest}} cover this, along with 
the session-churn and grant-pent the entry both survives the race and recovers 
once the pending confirmation completes.


> Leader information permanently erased when a change event races a pending 
> confirmation
> --------------------------------------------------------------------------------------
>
>                 Key: FLINK-40114
>                 URL: https://issues.apache.org/jira/browse/FLINK-40114
>             Project: Flink
>          Issue Type: Bug
>          Components: Runtime / Coordination
>    Affects Versions: 2.4.0
>            Reporter: Prashant Bhardwaj
>            Priority: Major
>
> h3. Summary
> Since FLINK-36451 made leadership confirmation asynchronous, a component's 
> leader information can be *permanently deleted* from the HA backend (e.g. the 
> Kubernetes leader ConfigMap) after a leadership loss and re-acquisition.
> h3. Root cause
> Confirmation runs asynchronously ({{{}confirmLeadershipAsync{}}} on the 
> single-threaded {{{}leadershipOperationExecutor{}}}), while the 
> leader-information-change handler ({{{}onLeaderInformationChange{}}} → 
> {{{}notifyLeaderInformationChangeInternal{}}}) still runs synchronously on 
> the driver's watch thread. After a loss + re-acquisition, a component's local 
> confirmation is cleared but its stale entry remains in the HA store. If a 
> change event is handled before the re-granted session's confirmation lands, 
> the handler takes the [empty-confirmation reset 
> branch|https://github.com/apache/flink/blob/f9d81b2e11e5cff7469b523cc73ce34b1d28e5ab/flink-runtime/src/main/java/org/apache/flink/runtime/leaderelection/DefaultLeaderElectionService.java#L519]
>  and publishes empty, which on Kubernetes [removes the 
> key|https://github.com/apache/flink/blob/f9d81b2e11e5cff7469b523cc73ce34b1d28e5ab/flink-kubernetes/src/main/java/org/apache/flink/kubernetes/highavailability/KubernetesLeaderElectionDriver.java#L161].
>  Because the self-correcting path runs only once a confirmation exists, if 
> that confirmation never lands — due to a transient getConfigMap() failure or 
> session churn — the deletion is permanent. Triggered by any API-server 
> disruption (outage, rolling upgrade, or intermittent 5xx/429 under load).
> h3. Impact
> The victim is whichever component's latest-session confirmation fails to land:
>  * {{restserver}} erased → REST endpoint cannot be resolved (operator/clients 
> cannot reach the cluster).
>  * {{{}dispatcher{}}}/{{{}jobmanager{}}} erased → no job submission, no 
> TaskManager registration.
> The JobManager keeps running and believes it is a healthy leader, so it is 
> silent and does not self-recover.
> h3. Reproduction
> Reproducible deterministically as a unit test by driving 
> {{DefaultLeaderElectionService}} with a manually-triggered 
> {{leadershipOperationExecutor}} so the confirmation can be held mid-flight: a 
> component wins leadership and confirms a session, loses leadership (clearing 
> the local confirmation but leaving the stale entry in the store), then wins 
> again; while the new session's confirmation is still pending, a 
> leader-information-change event carrying the stale entry drops the 
> component's key.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to