Skip to content

Make RedshiftDeleteClusterOperator busy-retry window configurable#69499

Closed
seanghaeli wants to merge 2 commits into
apache:mainfrom
aws-mwaa:feature/redshift-delete-configurable-busy-retry
Closed

Make RedshiftDeleteClusterOperator busy-retry window configurable#69499
seanghaeli wants to merge 2 commits into
apache:mainfrom
aws-mwaa:feature/redshift-delete-configurable-busy-retry

Conversation

@seanghaeli

@seanghaeli seanghaeli commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Why

RedshiftDeleteClusterOperator retries on InvalidClusterStateFault when the cluster is mid-transition (e.g. pausing/resuming/resizing), but the retry budget was hardcoded to 10 attempts * 15s = 2.5 minutes. A cluster transition routinely takes much longer than that (a pause is ~2–15 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.

Example errors:

InvalidClusterState: There is an operation running on the Cluster.
Please try to delete it at a later time.

Unable to delete the cluster ... You can only delete clusters with PAUSED, ..., ACTIVE, ... lifecycles.

What

Promote the two hardcoded constants to __init__ parameters and raise the default so the retry outlasts a real transition:

  • busy_retry_attempts: int = 60
  • busy_retry_interval: int = 15

Default is now 60 * 15s = 15 minutes, and both are overridable.

This retry loop is synchronous and runs before the deferrable branch, so in deferrable mode it still blocks the worker for the retry window. This PR fixes the synchronous path (the reported leak). The correct async fix for deferrable mode is a larger change that adds a new serialized trigger, so it will be a separate follow-up PR to keep this one small and focused.

End-to-end before/after test verification

Verified against a real Redshift cluster:

BEFORE (stock main, 2.5-min budget) — task FAILS, cluster leaks:

20:38:51  Cluster in resizing state, unable to delete. 9 attempts remaining.
20:39:06  ... 8 attempts remaining.
   ... (15s sleeps) ...
20:40:54  ... 1 attempts remaining.
botocore.errorfactory.InvalidClusterStateFault: An error occurred (InvalidClusterState)
  when calling the DeleteCluster operation: Unable to delete the cluster ...
DagRun ... state=failed   TASK_EXIT=1

AFTER (this PR, 15-min budget) — task SUCCEEDS, cluster deleted:

20:42:27  Cluster in resizing state, unable to delete. 59 attempts remaining.
20:42:43  ... 58 attempts remaining.
20:43:29  ... 55 attempts remaining.      <-- already past where stock main died
   ...
20:44:46  ... 50 attempts remaining.
20:45:01  Task instance state updated ... new_state=success   <-- delete accepted once resize settled
DagRun ... state=success   TASK_EXIT=0

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)
@boring-cyborg boring-cyborg Bot added area:providers provider:amazon AWS/Amazon - related issues labels Jul 6, 2026
@seanghaeli seanghaeli changed the title Make RedshiftDeleteClusterOperator busy-retry window configurable Make RedshiftDeleteClusterOperator delete reliably during cluster transitions Jul 6, 2026
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)
@seanghaeli seanghaeli force-pushed the feature/redshift-delete-configurable-busy-retry branch from 46b1088 to 737bb87 Compare July 6, 2026 23:51

@vincbeck vincbeck left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why creating new parameters for that? It seems unnecessary. Why not just increasing the value of existing parameters?

@seanghaeli

Copy link
Copy Markdown
Contributor Author

@vincbeck We could just make the default way longer but this does synchronously wait so we shouldn’t unnecessarily use resources to wait. Parameters seem to strike to balance of being usually short but configurable to longer if needed

@seanghaeli seanghaeli changed the title Make RedshiftDeleteClusterOperator delete reliably during cluster transitions Make RedshiftDeleteClusterOperator busy-retry window configurable Jul 7, 2026
@o-nikolas

Copy link
Copy Markdown
Contributor

Why creating new parameters for that? It seems unnecessary. Why not just increasing the value of existing parameters?

@vincbeck We could just make the default way longer but this does synchronously wait so we shouldn’t unnecessarily use resources to wait. Parameters seem to strike to balance of being usually short but configurable to longer if needed

I'm not sure what you mean by being "usually short"? It looks like the default is set to 15mins for those parameters? So the idea of making them short and extending if needed seems like it's not being used here. In reality, I think most people just want the operator to work, even if they have to wait, since the other outcome is much worse. So I'd maybe just simplify them back to constant values like before. But I don't feel tooo strongly about it

@seanghaeli

seanghaeli commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

@o-nikolas As in the default is 2.5 mins but then you can configure it to be longer in your case if you need to. I can also make the default case just be 20 minutes which could capture most situations. On the other hand, waiting dynamically for a completion signal would require new features in the triggerer that I'm not sure is worth writing currently, but maybe we decide this approach is justified.

Is there any downside to making this an optional parameter?

@seanghaeli seanghaeli closed this Jul 7, 2026
@seanghaeli

Copy link
Copy Markdown
Contributor Author

Closing this in favor of #69574 which implements the async handling as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:providers provider:amazon AWS/Amazon - related issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants