From c78ad9742eaac90f5b3bd29f1775ec81f8396bf8 Mon Sep 17 00:00:00 2001 From: Dongie Agnir Date: Tue, 28 Apr 2026 11:51:36 -0700 Subject: [PATCH] Update DDB retry policy with v2.1 constants In 2.1: - base delay : 25ms (existing) - max attempts: 4 (down from 9) --- ...StandardRetryStrategyV21ConstantsTest.java | 45 ++++++++++++++----- services/dynamodb/pom.xml | 6 +++ .../dynamodb/DynamoDbRetryPolicy.java | 25 ++++++++--- .../dynamodb/DynamoDbRetryPolicyTest.java | 20 +++++++++ 4 files changed, 79 insertions(+), 17 deletions(-) diff --git a/core/retries/src/test/java/software/amazon/awssdk/retries/StandardRetryStrategyV21ConstantsTest.java b/core/retries/src/test/java/software/amazon/awssdk/retries/StandardRetryStrategyV21ConstantsTest.java index 16dd17307694..cd772869e307 100644 --- a/core/retries/src/test/java/software/amazon/awssdk/retries/StandardRetryStrategyV21ConstantsTest.java +++ b/core/retries/src/test/java/software/amazon/awssdk/retries/StandardRetryStrategyV21ConstantsTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import java.time.Duration; +import java.util.function.Supplier; import org.junit.jupiter.api.Test; import software.amazon.awssdk.retries.api.AcquireInitialTokenResponse; import software.amazon.awssdk.retries.api.BackoffStrategy; @@ -82,24 +83,33 @@ void v20_throttlingRetry_deducts5Tokens() { @Test void v21Enabled_backoffUses50msBaseDelay() { - RetryStrategy strategy = StandardRetryStrategy.builder(true) - .retryOnException(t -> true) - .build(); - RefreshRetryTokenResponse response = refreshToken(strategy, new RuntimeException("err")); - // First retry delay should be in [0, 50ms] (jittered) - assertThat(response.delay()).isBetween(Duration.ZERO, Duration.ofMillis(50)); + Supplier stratSupplier = () -> StandardRetryStrategy.builder(true) + .retryOnException(t -> true) + .build(); + + probabilisticAssertDelayBetween(stratSupplier, new RuntimeException("err"), Duration.ZERO, Duration.ofMillis(50)); + } + + @Test + void v21Enabled_throttlingBackoffUses1000msBaseDelay() { + + Supplier stratSupplier = () -> StandardRetryStrategy.builder(true) + .retryOnException(t -> true) + .treatAsThrottling(t -> true) + .build(); + + probabilisticAssertDelayBetween(stratSupplier, new RuntimeException("err"), Duration.ZERO, Duration.ofMillis(1000)); } @Test void v20_backoffUses100msBaseDelay() { - RetryStrategy strategy = StandardRetryStrategy.builder(false) - .retryOnException(t -> true) - .build(); + Supplier stratSupplier = () -> StandardRetryStrategy.builder(false) + .retryOnException(t -> true) + .build(); + + probabilisticAssertDelayBetween(stratSupplier, new RuntimeException("err"), Duration.ZERO, Duration.ofMillis(100)); - RefreshRetryTokenResponse response = refreshToken(strategy, new RuntimeException("err")); - // First retry delay should be in [0, 100ms] (jittered) - assertThat(response.delay()).isBetween(Duration.ZERO, Duration.ofMillis(100)); } @Test @@ -135,4 +145,15 @@ private RefreshRetryTokenResponse refreshToken(RetryStrategy strategy, Exception return strategy.refreshRetryToken( RefreshRetryTokenRequest.builder().token(initial.token()).failure(failure).build()); } + + // Backoffs are jittered, so verify it by testing it many times + private void probabilisticAssertDelayBetween(Supplier strategy, + Exception failure, + Duration min, + Duration max) { + for (int i = 0; i < 128; ++i) { + RefreshRetryTokenResponse response = refreshToken(strategy.get(), failure); + assertThat(response.delay()).isBetween(min, max); + } + } } diff --git a/services/dynamodb/pom.xml b/services/dynamodb/pom.xml index d5f3a2e7fc6f..ebcbcf07ab46 100644 --- a/services/dynamodb/pom.xml +++ b/services/dynamodb/pom.xml @@ -78,5 +78,11 @@ http-auth-aws ${awsjavasdk.version} + + software.amazon.awssdk + retries + ${awsjavasdk.version} + test + diff --git a/services/dynamodb/src/main/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicy.java b/services/dynamodb/src/main/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicy.java index 56f812528749..c9f30849a926 100644 --- a/services/dynamodb/src/main/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicy.java +++ b/services/dynamodb/src/main/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicy.java @@ -25,6 +25,7 @@ import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.core.internal.retry.RetryPolicyAdapter; import software.amazon.awssdk.core.internal.retry.SdkDefaultRetrySetting; +import software.amazon.awssdk.core.retry.NewRetries2026Resolver; import software.amazon.awssdk.core.retry.RetryMode; import software.amazon.awssdk.core.retry.RetryPolicy; import software.amazon.awssdk.core.retry.backoff.BackoffStrategy; @@ -41,12 +42,17 @@ final class DynamoDbRetryPolicy { /** * Default max retry count for DynamoDB client, regardless of retry mode. **/ - private static final int MAX_ERROR_RETRY = 8; + private static final int MAX_ERROR_RETRY_V20 = 8; /** * Default attempts count for DynamoDB client, regardless of retry mode. **/ - private static final int MAX_ATTEMPTS = MAX_ERROR_RETRY + 1; + private static final int MAX_ATTEMPTS_V20 = MAX_ERROR_RETRY_V20 + 1; + + /** + * Default attempts count for DynamoDB client, regardless of retry mode. (v2.1) + **/ + private static final int MAX_ATTEMPTS_V21 = 4; /** * Default base sleep time for DynamoDB, regardless of retry mode. @@ -95,9 +101,12 @@ public static RetryStrategy resolveRetryStrategy(SdkClientConfiguration config) .build(); } - return AwsRetryStrategy.forRetryMode(retryMode) + boolean newRetries2026Enabled = isNewRetries2026Enabled(config); + int maxAttempts = newRetries2026Enabled ? MAX_ATTEMPTS_V21 : MAX_ATTEMPTS_V20; + + return AwsRetryStrategy.forRetryMode(retryMode, newRetries2026Enabled) .toBuilder() - .maxAttempts(MAX_ATTEMPTS) + .maxAttempts(maxAttempts) .backoffStrategy(exponentialDelay(BASE_DELAY, SdkDefaultRetrySetting.MAX_BACKOFF)) .build(); } @@ -106,7 +115,7 @@ private static RetryPolicy retryPolicyFor(RetryMode retryMode) { return AwsRetryPolicy.forRetryMode(retryMode) .toBuilder() .additionalRetryConditionsAllowed(false) - .numRetries(MAX_ERROR_RETRY) + .numRetries(MAX_ERROR_RETRY_V20) .backoffStrategy(BACKOFF_STRATEGY) .build(); } @@ -116,6 +125,12 @@ private static RetryMode resolveRetryMode(SdkClientConfiguration config) { .profileFile(config.option(SdkClientOption.PROFILE_FILE_SUPPLIER)) .profileName(config.option(SdkClientOption.PROFILE_NAME)) .defaultRetryMode(config.option(SdkClientOption.DEFAULT_RETRY_MODE)) + .defaultNewRetries2026(config.option(SdkClientOption.DEFAULT_NEW_RETRIES_2026)) .resolve(); } + + private static boolean isNewRetries2026Enabled(SdkClientConfiguration config) { + Boolean defaultNewRetries2026 = config.option(SdkClientOption.DEFAULT_NEW_RETRIES_2026); + return new NewRetries2026Resolver().defaultNewRetries2026(defaultNewRetries2026).resolve(); + } } diff --git a/services/dynamodb/src/test/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicyTest.java b/services/dynamodb/src/test/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicyTest.java index bcb25c2504d6..cb489a6a30b6 100644 --- a/services/dynamodb/src/test/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicyTest.java +++ b/services/dynamodb/src/test/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicyTest.java @@ -5,12 +5,16 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import software.amazon.awssdk.core.SdkSystemSetting; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.core.internal.retry.SdkDefaultRetryStrategy; import software.amazon.awssdk.core.retry.RetryMode; import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.retries.LegacyRetryStrategy; +import software.amazon.awssdk.retries.StandardRetryStrategy; import software.amazon.awssdk.retries.api.RetryStrategy; import software.amazon.awssdk.testutils.EnvironmentVariableHelper; import software.amazon.awssdk.utils.StringInputStream; @@ -126,4 +130,20 @@ void resolve_retryModeNotSetWithEnvNorSupplier_resolvesFromSdkDefault() { assertThat(retryMode).isEqualTo(RetryMode.LEGACY); } + @ParameterizedTest(name = "V2.1 retries = {0}, max attempts = {1}") + @CsvSource({ + "false,9", + "true,4" + }) + void resolve_maxAttemptsAndStrategyCompliantWithRetriesVersion(boolean retries21, int expectedMaxAttempts) { + SdkClientConfiguration cfg = SdkClientConfiguration.builder() + .option(SdkClientOption.DEFAULT_NEW_RETRIES_2026, retries21) + .build(); + RetryStrategy strategy = DynamoDbRetryPolicy.resolveRetryStrategy(cfg); + + Class expectedStrategy = retries21 ? StandardRetryStrategy.class : LegacyRetryStrategy.class; + + assertThat(strategy).isInstanceOf(expectedStrategy); + assertThat(strategy.maxAttempts()).isEqualTo(expectedMaxAttempts); + } }