Skip to content

Add adaptive retry strategy #3975

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.awssdk.retries;

import java.util.function.Predicate;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.annotations.ThreadSafe;
import software.amazon.awssdk.retries.api.AcquireInitialTokenRequest;
import software.amazon.awssdk.retries.api.BackoffStrategy;
import software.amazon.awssdk.retries.api.RetryStrategy;
import software.amazon.awssdk.retries.internal.AdaptiveRetryStrategyImpl;
import software.amazon.awssdk.retries.internal.circuitbreaker.TokenBucketStore;
import software.amazon.awssdk.retries.internal.ratelimiter.RateLimiterTokenBucketStore;

/**
* The adaptive retry strategy is a {@link RetryStrategy} when executing against a very resource-constrained set of resources.
* <p>
* Unlike {@link StandardRetryStrategy}, care should be taken when using this strategy. Specifically, it should be used:
* <ol>
* <li>When the availability of downstream resources are mostly affected by callers that are also using
* the {@link AdaptiveRetryStrategy}.
* <li>The scope (either the whole strategy or the {@link AcquireInitialTokenRequest#scope}) of the strategy is constrained
* to target "resource", so that availability issues in one resource cannot delay other, unrelated resource's availability.
* <p>
* The adaptive retry strategy by default:
* <ol>
* <li>Retries on the conditions configured in the {@link Builder}.
* <li>Retries 2 times (3 total attempts). Adjust with {@link Builder#maxAttempts}
* <li>Uses a dynamic backoff delay based on load currently perceived against the downstream resource
* <li>Circuit breaking (disabling retries) in the event of high downstream failures within an individual scope.
* Circuit breaking may prevent a first attempt in outage scenarios to protect the downstream service.
* </ol>
*
* @see StandardRetryStrategy
*/
@SdkPublicApi
@ThreadSafe
public interface AdaptiveRetryStrategy extends RetryStrategy<AdaptiveRetryStrategy.Builder, AdaptiveRetryStrategy> {

/**
* Create a new {@link AdaptiveRetryStrategy.Builder}.
*
* <p>Example Usage
* <pre>
* AdaptiveRetryStrategy retryStrategy =
* AdaptiveRetryStrategy.builder()
* .retryOnExceptionInstanceOf(IllegalArgumentException.class)
* .retryOnExceptionInstanceOf(IllegalStateException.class)
* .build();
* </pre>
*/
static AdaptiveRetryStrategy.Builder builder() {
return AdaptiveRetryStrategyImpl
.builder()
.maxAttempts(DefaultRetryStrategy.Standard.MAX_ATTEMPTS)
.tokenBucketStore(TokenBucketStore.builder()
.tokenBucketMaxCapacity(DefaultRetryStrategy.Standard.TOKEN_BUCKET_SIZE)
.build())
.tokenBucketExceptionCost(DefaultRetryStrategy.Standard.TOKEN_BUCKET_SIZE)
.backoffStrategy(BackoffStrategy.exponentialDelay(DefaultRetryStrategy.Standard.BASE_DELAY,
DefaultRetryStrategy.Standard.MAX_BACKOFF))
.rateLimiterTokenBucketStore(RateLimiterTokenBucketStore.builder().build());
}

@Override
Builder toBuilder();

interface Builder extends RetryStrategy.Builder<Builder, AdaptiveRetryStrategy> {
/**
* Configure the predicate to allow the strategy categorize a Throwable as throttling exception.
*/
Builder treatAsThrottling(Predicate<Throwable> treatAsThrottling);


@Override
AdaptiveRetryStrategy build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,35 @@ private DefaultRetryStrategy() {
public static StandardRetryStrategy.Builder standardStrategyBuilder() {
return StandardRetryStrategy.builder()
.maxAttempts(Standard.MAX_ATTEMPTS)
.backoffStrategy(BackoffStrategy.exponentialDelay(Standard.BASE_DELAY, Standard.MAX_BACKOFF))
.circuitBreakerEnabled(true);
.backoffStrategy(BackoffStrategy.exponentialDelay(Standard.BASE_DELAY, Standard.MAX_BACKOFF));
}

/**
* Create a new builder for a {@code AdaptiveRetryStrategy}.
*
* <p>Example Usage
* <pre>
* AdaptiveRetryStrategy retryStrategy =
* RetryStrategies.adaptiveStrategyBuilder()
* .retryOnExceptionInstanceOf(IllegalArgumentException.class)
* .retryOnExceptionInstanceOf(IllegalStateException.class)
* .build();
* </pre>
*/
public static AdaptiveRetryStrategy.Builder adaptiveRetryStrategyBuilder() {
return AdaptiveRetryStrategy.builder()
.maxAttempts(Adaptive.MAX_ATTEMPTS);
}

static final class Standard {
static final int MAX_ATTEMPTS = 3;
static final Duration BASE_DELAY = Duration.ofSeconds(1);
static final Duration MAX_BACKOFF = Duration.ofSeconds(20);
static final int TOKEN_BUCKET_SIZE = 500;
static final int DEFAULT_EXCEPTION_TOKEN_COST = 5;
}

static final class Adaptive {
static final int MAX_ATTEMPTS = 3;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import software.amazon.awssdk.retries.api.BackoffStrategy;
import software.amazon.awssdk.retries.api.RetryStrategy;
import software.amazon.awssdk.retries.internal.StandardRetryStrategyImpl;
import software.amazon.awssdk.retries.internal.circuitbreaker.TokenBucketStore;

/**
* The standard retry strategy is the recommended {@link RetryStrategy} for normal use-cases.
Expand Down Expand Up @@ -55,7 +56,14 @@ public interface StandardRetryStrategy extends RetryStrategy<StandardRetryStrate
* </pre>
*/
static Builder builder() {
return StandardRetryStrategyImpl.builder();
return StandardRetryStrategyImpl
.builder()
.maxAttempts(DefaultRetryStrategy.Standard.MAX_ATTEMPTS)
.tokenBucketStore(TokenBucketStore
.builder()
.tokenBucketMaxCapacity(DefaultRetryStrategy.Standard.TOKEN_BUCKET_SIZE)
.build())
.tokenBucketExceptionCost(DefaultRetryStrategy.Standard.TOKEN_BUCKET_SIZE);
}

@Override
Expand Down
Loading