Deduplicate Kubernetes observer state checks and diagnosis logs for unchanged Pending pods - #22965
Conversation
…nchanged Pending pods Deduplicate diagnoses by normalized causes so scheduler count changes do not produce duplicate flow run logs, cache completed Pending state checks per pod until the phase changes while retrying failures and timeouts, and do not cache a proposal rejected with a Scheduled replacement. OSS-8181 Co-authored-by: Alexander Streed <alex.s@prefect.io>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
chuqCTC
left a comment
There was a problem hiding this comment.
I found three correctness gaps compared with the issue's intended behavior.
Co-authored-by: Alexander Streed <alex.s@prefect.io>
chuqCTC
left a comment
There was a problem hiding this comment.
I found two remaining edge cases in the scheduler-message normalization.
chuqCTC
left a comment
There was a problem hiding this comment.
One additional state-transition edge case and one non-blocking simplification from a cleanup pass.
Co-authored-by: Alexander Streed <alex.s@prefect.io>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Co-authored-by: Alexander Streed <alex.s@prefect.io>
| @pytest.fixture(autouse=True) | ||
| def clear_observer_lifecycle_caches() -> Iterator[None]: | ||
| _completed_state_check_cache.clear() | ||
| _last_diagnosis_cache.clear() | ||
| observer._pod_cache_reconciliation_touches.clear() | ||
| yield | ||
| _completed_state_check_cache.clear() | ||
| _last_diagnosis_cache.clear() | ||
| observer._pod_cache_reconciliation_touches.clear() |
There was a problem hiding this comment.
| else: | ||
| _last_diagnosis_cache[uid] = key |
closes #22964
This PR stops the Kubernetes observer from repeating flow run state checks and diagnosis logs when a pod stays in the
Pendingphase.Kubernetes sends many watch events for a pod that does not change. Before this change, each event caused a new
read_flow_runcall, a newpropose_statecall, and a possible duplicate flow run log. This PR makes these operations occur one time for each unchanged condition:Unschedulablediagnoses, it normalizes only complete, recognized Kubernetes scheduler formats. Supported filter counts and reason ordering, the Kubernetes v1.35 NodeDeclaredFeatures requirement list, and default-preemption summaries are canonicalized while general post-filter text remains exact. Unknown or malformed scheduler output uses the exact original detail, so upstream format changes can produce an extra log instead of suppressing a meaningfully changed diagnosis.Pending, diagnosis keys are cleared on recovery, and empty records are removed. Deletion and shutdown release the whole record. A periodic, paginated Kubernetes listing also releases state for pods whose deletion event was missed, but only after the full watched namespace and label scope is listed successfully. Live pods and UIDs observed concurrently with reconciliation remain retained.Pendingphase. Transient failures, timeouts, and rejected proposals are not retained, so the observer retries them on the next event.propose_state()returns. It records the check as completed only when the returned state isInfrastructurePendingor is running, final, paused, or cancelling. Any other replacement state, such as aScheduledstate after a rejected proposal, is retried on the next event.Implementation details
diagnostics.py: addsInfrastructureDiagnosis._dedupe_key(). RecognizedUnschedulable*scheduler messages use a namespaced normalized key; unfamiliar or malformed messages use a namespaced exact key. The parser requires the expected scheduler scaffold, fully consumes counted reason histograms, and accepts only the Kubernetes reason shapes covered by the observer's scheduler-message corpus. It tags scheduler stages, preserves arbitrary post-filter text, validates nested default-preemption summaries independently, and sorts NodeDeclaredFeatures requirements only when that complete reason matches. All other diagnosis categories remain exact.observer.py:_PodLifecycleStatesowns one record per relevant pod UID, containing the completed Pending-state check and last successfully handed-off diagnosis key.Pendingand clears diagnosis keys on recovery. Deletion releases the whole pod record even if event handling fails, and observer shutdown clears the owner after reconciliation stops._is_completed_flow_run_state()is used both for the state read before the proposal and for the state thatpropose_state()returns.ObjectNotFoundfromread_flow_runcompletes the check because a missing flow run is a terminal observation for an immutable flow run ID.propose_state()raisesPauseinstead of returning a paused replacement state, andPauseis aBaseExceptionsubclass. The observer catches it so the signal does not escape the event handler and records the check as completed.flow_run_loggercall returns successfully. Durable API delivery remains owned by the existing Prefect logging pipeline rather than the observer.Kopf multiplexes the watch stream into one queue for each object UID and processes each queue with one worker, so two events for the same pod cannot run in the handler at the same time. Reconciliation separately protects any UID touched while its Kubernetes listing is in flight.
Checklist
<link to issue>"mint.json.Link to Devin session: https://app.devin.ai/sessions/a3dd1abe570d4a97b6ab97350a8a06fb
Open in Devin Desktop: https://app.devin.ai/desktop/session/a3dd1abe570d4a97b6ab97350a8a06fb?variant=devin