-
Notifications
You must be signed in to change notification settings - Fork 89
Fix EC2 throttling during computenode launch is not retried and is reported as ICE when using multi InstanceTypes or SubnetIds #734
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,6 +30,17 @@ | |
| INSTANCE_INFO_RETRIEVAL_TIMEOUT_DEFAULT = 90 | ||
| INSTANCE_INFO_RETRIEVAL_MAX_BACKOFF = 30 | ||
|
|
||
| # The only error code EC2 returns when a launch request is throttled, see | ||
| # https://docs.aws.amazon.com/ec2/latest/devguide/ec2-api-throttling.html | ||
| LAUNCH_THROTTLING_ERROR_CODE = "RequestLimitExceeded" | ||
|
|
||
| # An override that CreateFleet did not fulfill because another override failed is reported with this exact | ||
| # code and message pair, which points at the real error rather than being one. | ||
| UNFULFILLED_OVERRIDE_ERROR = ( | ||
| "UnfulfillableCapacity", | ||
| "Failed to fulfill capacity. Please review errors in the response.", | ||
| ) | ||
|
|
||
|
|
||
| class EC2Instance: | ||
| def __init__(self, id, private_ip, hostname, all_private_ips, launch_time): | ||
|
|
@@ -430,7 +441,27 @@ def _launch_instances(self, launch_params): | |
| if partial_instance_ids: | ||
| logger.error("Unable to retrieve instance info for instances: %s", partial_instance_ids) | ||
|
|
||
| if not instances and len(err_list) == 1: | ||
| if not instances: | ||
| # Drop the entries that only point at the real error, unless the response carries nothing else. | ||
| real_errors = [ | ||
| err | ||
| for err in err_list | ||
| if (err.get("ErrorCode"), err.get("ErrorMessage")) != UNFULFILLED_OVERRIDE_ERROR | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. UnfulfillableCapacity is real error to right?
Is it safe to drop it?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes it's safe. The filter matches the code and the error message as a pair, not the code The code+message pair we dropped is: Every other And the message itself is a evidence. "Please review errors in the response" means something if the response carries another error to review. As the sole entry it would be self-referential. |
||
| ] | ||
| if real_errors: | ||
| err_list = real_errors | ||
| # A single cause is normally left. Should there be several, prefer throttling as a safety net: it is | ||
| # the only cause that resolves on its own, and reporting it as insufficient capacity would instead | ||
| # fail the compute resource over. | ||
| throttling = next( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with the safety net approach, but not on limiting it to throttling.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a real behavior change and we should not do it without clear decision doc. Other error code will be handled in other code path. It's safe to only handle and retry the throttling error here - the exponential backoff is designed only for it. Also I raise a doubt here:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that we should not limit it to throttling. If there are other errors due to real issues, it does not make sense to retry when it will fail anyways. Also, it there is more than one error code, then the current behavior is to also not retry. I think we should keep this existing behavior. |
||
| (err for err in err_list if err.get("ErrorCode") == LAUNCH_THROTTLING_ERROR_CODE), None | ||
| ) | ||
| if throttling: | ||
| raise LaunchInstancesError(throttling.get("ErrorCode"), throttling.get("ErrorMessage")) | ||
| # Normally a single cause is left. Reporting the first one of several is a second safety net: the | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I imagine that you are keeping this approach of bubbling up only the first error to keep the fix minimal and I agree on the overall minimization approach. However:
Example: if the request fails due to ICE + another unretriable error E2, but E2 is listed first, we will bubble up only E2. What would be the consequence?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to clarify - if everything goes correct and well, after the fix, after removing the duplicated UnfulfillableCapacity, the
To introduce the minimal changes, because node package is sensitive and I want to avoid any regression possibility.
Good question, I tried once, but other code chain also use this returned error. To do it we need to restructure and it introduce too much risky code changes. To solve an edge case, I don't think it deserve.
Our code logic will treat it as an ICE issue and trigger fast failover mechanism. Which aligns with today's(3.16.0) behavior. If it's the opposite, another unretriable error E1 + ICE, we fail and requeue job without retry, which fix today's wrong behavior to the correct behavior.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we are using a fallback to always retry if there is throttling, why bubble up into one error code. It seems like the behavior would be the same whether we do that or not. |
||
| # caller otherwise records a hardcoded InsufficientInstanceCapacity, and any code EC2 actually | ||
| # returned is more useful than an invented one, whichever of them the response happens to list first. | ||
| if not instances and err_list: | ||
| raise LaunchInstancesError(err_list[0].get("ErrorCode"), err_list[0].get("ErrorMessage")) | ||
| return {"Instances": instances} | ||
| except ClientError as e: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does "override" mean here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It means the launch template "override".