Skip to content

Commit dd63b90

Browse files
authored
Add adaptive retry strategy (#3975)
* Add adaptive retry strategy * Address pull request comments * Address PR comments * Address PR comments
1 parent a806cd7 commit dd63b90

14 files changed

+1833
-12
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.retries;
17+
18+
import java.util.function.Predicate;
19+
import software.amazon.awssdk.annotations.SdkPublicApi;
20+
import software.amazon.awssdk.annotations.ThreadSafe;
21+
import software.amazon.awssdk.retries.api.AcquireInitialTokenRequest;
22+
import software.amazon.awssdk.retries.api.BackoffStrategy;
23+
import software.amazon.awssdk.retries.api.RetryStrategy;
24+
import software.amazon.awssdk.retries.internal.AdaptiveRetryStrategyImpl;
25+
import software.amazon.awssdk.retries.internal.circuitbreaker.TokenBucketStore;
26+
import software.amazon.awssdk.retries.internal.ratelimiter.RateLimiterTokenBucketStore;
27+
28+
/**
29+
* The adaptive retry strategy is a {@link RetryStrategy} when executing against a very resource-constrained set of resources.
30+
* <p>
31+
* Unlike {@link StandardRetryStrategy}, care should be taken when using this strategy. Specifically, it should be used:
32+
* <ol>
33+
* <li>When the availability of downstream resources are mostly affected by callers that are also using
34+
* the {@link AdaptiveRetryStrategy}.
35+
* <li>The scope (either the whole strategy or the {@link AcquireInitialTokenRequest#scope}) of the strategy is constrained
36+
* to target "resource", so that availability issues in one resource cannot delay other, unrelated resource's availability.
37+
* <p>
38+
* The adaptive retry strategy by default:
39+
* <ol>
40+
* <li>Retries on the conditions configured in the {@link Builder}.
41+
* <li>Retries 2 times (3 total attempts). Adjust with {@link Builder#maxAttempts}
42+
* <li>Uses a dynamic backoff delay based on load currently perceived against the downstream resource
43+
* <li>Circuit breaking (disabling retries) in the event of high downstream failures within an individual scope.
44+
* Circuit breaking may prevent a first attempt in outage scenarios to protect the downstream service.
45+
* </ol>
46+
*
47+
* @see StandardRetryStrategy
48+
*/
49+
@SdkPublicApi
50+
@ThreadSafe
51+
public interface AdaptiveRetryStrategy extends RetryStrategy<AdaptiveRetryStrategy.Builder, AdaptiveRetryStrategy> {
52+
53+
/**
54+
* Create a new {@link AdaptiveRetryStrategy.Builder}.
55+
*
56+
* <p>Example Usage
57+
* <pre>
58+
* AdaptiveRetryStrategy retryStrategy =
59+
* AdaptiveRetryStrategy.builder()
60+
* .retryOnExceptionInstanceOf(IllegalArgumentException.class)
61+
* .retryOnExceptionInstanceOf(IllegalStateException.class)
62+
* .build();
63+
* </pre>
64+
*/
65+
static AdaptiveRetryStrategy.Builder builder() {
66+
return AdaptiveRetryStrategyImpl
67+
.builder()
68+
.maxAttempts(DefaultRetryStrategy.Standard.MAX_ATTEMPTS)
69+
.tokenBucketStore(TokenBucketStore.builder()
70+
.tokenBucketMaxCapacity(DefaultRetryStrategy.Standard.TOKEN_BUCKET_SIZE)
71+
.build())
72+
.tokenBucketExceptionCost(DefaultRetryStrategy.Standard.TOKEN_BUCKET_SIZE)
73+
.backoffStrategy(BackoffStrategy.exponentialDelay(DefaultRetryStrategy.Standard.BASE_DELAY,
74+
DefaultRetryStrategy.Standard.MAX_BACKOFF))
75+
.rateLimiterTokenBucketStore(RateLimiterTokenBucketStore.builder().build());
76+
}
77+
78+
@Override
79+
Builder toBuilder();
80+
81+
interface Builder extends RetryStrategy.Builder<Builder, AdaptiveRetryStrategy> {
82+
/**
83+
* Configure the predicate to allow the strategy categorize a Throwable as throttling exception.
84+
*/
85+
Builder treatAsThrottling(Predicate<Throwable> treatAsThrottling);
86+
87+
88+
@Override
89+
AdaptiveRetryStrategy build();
90+
}
91+
}

core/retries/src/main/java/software/amazon/awssdk/retries/DefaultRetryStrategy.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,35 @@ private DefaultRetryStrategy() {
4444
public static StandardRetryStrategy.Builder standardStrategyBuilder() {
4545
return StandardRetryStrategy.builder()
4646
.maxAttempts(Standard.MAX_ATTEMPTS)
47-
.backoffStrategy(BackoffStrategy.exponentialDelay(Standard.BASE_DELAY, Standard.MAX_BACKOFF))
48-
.circuitBreakerEnabled(true);
47+
.backoffStrategy(BackoffStrategy.exponentialDelay(Standard.BASE_DELAY, Standard.MAX_BACKOFF));
48+
}
49+
50+
/**
51+
* Create a new builder for a {@code AdaptiveRetryStrategy}.
52+
*
53+
* <p>Example Usage
54+
* <pre>
55+
* AdaptiveRetryStrategy retryStrategy =
56+
* RetryStrategies.adaptiveStrategyBuilder()
57+
* .retryOnExceptionInstanceOf(IllegalArgumentException.class)
58+
* .retryOnExceptionInstanceOf(IllegalStateException.class)
59+
* .build();
60+
* </pre>
61+
*/
62+
public static AdaptiveRetryStrategy.Builder adaptiveRetryStrategyBuilder() {
63+
return AdaptiveRetryStrategy.builder()
64+
.maxAttempts(Adaptive.MAX_ATTEMPTS);
4965
}
5066

5167
static final class Standard {
5268
static final int MAX_ATTEMPTS = 3;
5369
static final Duration BASE_DELAY = Duration.ofSeconds(1);
5470
static final Duration MAX_BACKOFF = Duration.ofSeconds(20);
71+
static final int TOKEN_BUCKET_SIZE = 500;
72+
static final int DEFAULT_EXCEPTION_TOKEN_COST = 5;
73+
}
74+
75+
static final class Adaptive {
76+
static final int MAX_ATTEMPTS = 3;
5577
}
5678
}

core/retries/src/main/java/software/amazon/awssdk/retries/StandardRetryStrategy.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import software.amazon.awssdk.retries.api.BackoffStrategy;
2121
import software.amazon.awssdk.retries.api.RetryStrategy;
2222
import software.amazon.awssdk.retries.internal.StandardRetryStrategyImpl;
23+
import software.amazon.awssdk.retries.internal.circuitbreaker.TokenBucketStore;
2324

2425
/**
2526
* The standard retry strategy is the recommended {@link RetryStrategy} for normal use-cases.
@@ -55,7 +56,14 @@ public interface StandardRetryStrategy extends RetryStrategy<StandardRetryStrate
5556
* </pre>
5657
*/
5758
static Builder builder() {
58-
return StandardRetryStrategyImpl.builder();
59+
return StandardRetryStrategyImpl
60+
.builder()
61+
.maxAttempts(DefaultRetryStrategy.Standard.MAX_ATTEMPTS)
62+
.tokenBucketStore(TokenBucketStore
63+
.builder()
64+
.tokenBucketMaxCapacity(DefaultRetryStrategy.Standard.TOKEN_BUCKET_SIZE)
65+
.build())
66+
.tokenBucketExceptionCost(DefaultRetryStrategy.Standard.TOKEN_BUCKET_SIZE);
5967
}
6068

6169
@Override

0 commit comments

Comments
 (0)