Make RedshiftDeleteClusterOperator delete reliably during cluster transitions#69499
Draft
seanghaeli wants to merge 2 commits into
Draft
Make RedshiftDeleteClusterOperator delete reliably during cluster transitions#69499seanghaeli wants to merge 2 commits into
seanghaeli wants to merge 2 commits into
Conversation
The delete operator retries on InvalidClusterStateFault when the cluster is mid-transition (e.g. pausing/resuming). The retry budget was hardcoded to 10 attempts * 15s = 2.5 min, which expires long before a ~15 min pause settles into a deletable state, causing the task to fail and the cluster to leak. Promote the two hardcoded constants to __init__ params (busy_retry_attempts, busy_retry_interval) and raise the default to 60 * 15s = 15 min so the retry outlasts a pause. Both are overridable. poll_interval/max_attempts (completion waiter) are unchanged. Note: this synchronous retry loop still runs before the deferrable branch, so it blocks the worker in both sync and deferrable modes. Moving the busy retry into the async trigger for deferrable mode is a follow-up. Generated-by: Claude Code (Opus)
In deferrable mode the operator no longer runs the synchronous busy-retry loop that blocked the worker/triggerer for up to the ~15 minute busy-retry window. Instead it attempts the delete once and, if the cluster is mid-transition (InvalidClusterStateFault), defers to a new RedshiftClusterSettledTrigger that asynchronously polls until the cluster leaves every transitional lifecycle (creating/modifying/resizing/pausing/ resuming/deleting/rebooting/...). The _retry_delete_when_settled callback then re-issues the delete: on success it defers to the existing RedshiftDeleteClusterTrigger (cluster_deleted) -> execute_complete; on a race (still InvalidClusterStateFault) it re-defers to the settle-wait trigger, bounded by busy_retry_attempts. This mirrors the EksCreateCluster re-defer pattern. Sync mode (deferrable=False) keeps the existing synchronous busy-retry loop unchanged. A poll-until-settled trigger (rather than a single-target-status poller or a custom botocore "not-transitional" waiter) is used because a busy cluster settles into different terminal states depending on the in-flight operation (pausing -> paused, resizing -> available), and the delete is acceptable in any non-transitional lifecycle. Generated-by: Claude Code (Opus)
46b1088 to
737bb87
Compare
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.
Why
RedshiftDeleteClusterOperatorretries onInvalidClusterStateFaultwhen the cluster is mid-transition (e.g. pausing/resuming/resizing), but the retry budget was hardcoded to10 attempts * 15s = 2.5 minutes. A cluster transition routinely takes much longer than that (a pause is ~2–15 min, a classic resize ~6 min), so the loop exhausts and re-raises long before the cluster reaches a deletable state. The task fails and the cluster keeps running until manual deletion (a leak).Example errors seen mid-transition:
Unable to delete the cluster ... You can only delete clusters with PAUSED, ..., ACTIVE, ... lifecycles.What
Two changes, one per mode:
1. Non-deferrable mode — configurable, longer retry budget. Promote the two hardcoded constants to
__init__params and raise the default so the retry outlasts a real transition:busy_retry_attempts: int = 60busy_retry_interval: int = 15Default is now
60 * 15s = 15 minutes, both overridable. Fail-loud is preserved (re-raises once the budget is exhausted). The private_attempts/_attempt_intervalattributes are replaced by these params.2. Deferrable mode — async re-defer instead of a synchronous block. Previously the synchronous retry loop ran in deferrable mode too, blocking the worker (this was flagged by @ramitkataria on an earlier iteration — a 15-min sync block defeats the purpose of deferrable mode). Deferrable mode now:
InvalidClusterStateFaultit defers to a newRedshiftClusterSettledTriggerthat polls until the cluster is no longer in a transitional state,RedshiftDeleteClusterTrigger(waitscluster_deleted),No worker is blocked at any point. This mirrors the re-defer pattern in
EksCreateClusterOperator.deferrable_create_cluster_next.Testing
Unit tests (added/updated): configurable defaults/custom values, retry-then-raise, succeed-on-second-attempt (sync); defer-to-settle-trigger, callback re-issues delete, re-defer-on-race, error-event-raises (deferrable). All pass.
End-to-end before/after — non-deferrable mode (real AWS)
Identical DAG, identical ~6.5-minute transient (classic resize), controlled before/after against a real cluster.
BEFORE (stock
main, 2.5-min budget) — task FAILS, cluster leaks:AFTER (this PR, 15-min budget) — task SUCCEEDS, cluster deleted:
End-to-end — deferrable mode async re-defer (real AWS)
Same real-cluster + classic-resize setup,
deferrable=True. The operator deferred through the whole transient without blocking a worker:AWS-side timeline confirms:
resizing → available → deleting → ClusterNotFound. Cluster fully deleted, no leak. (A live race — the cluster momentarily bounced back to a transitional state right after settling — was handled correctly by the re-defer branch.)Generated-by: Claude Code (Opus)