This is an automated email from the ASF dual-hosted git repository.
Samrat002 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git
The following commit(s) were added to refs/heads/master by this push:
new 29655b36d7b [FLINK-41014][s3] Add an adaptive retry mode to the native
S3 connector
29655b36d7b is described below
commit 29655b36d7b64814df010c96b781060c97ad0c82
Author: Gabor Somogyi <[email protected]>
AuthorDate: Tue Nov 3 18:39:26 2026 +0100
[FLINK-41014][s3] Add an adaptive retry mode to the native S3 connector
* [FLINK-41014][s3] Add an adaptive retry mode to the native S3 connector
* [FLINK-41014][s3] Upgrade the AWS SDK of the native S3 connector to 2.57.2
The adaptive retry mode relies on the SDK's AdaptiveRetryStrategy, whose
client-side rate limiting was too aggressive (the allowed request rate
could end up far below what the object store serves) and unstable (it
kept varying although the server side rate was constant). That is fixed
by https://github.com/aws/aws-sdk-java-v2/pull/7197, first released in
SDK 2.52.0, so the SDK is moved to the latest release, 2.57.2.
- aws-crt is moved to 0.48.5, the version SDK 2.57.2 is built against.
download-crt-jars.sh resolves it from the SDK version and needs no
change apart from dropping an outdated example version.
- Since SDK 2.46.0 every service client pulls in apache5-client, which
becomes the default HTTP client of clients built without an explicit
one (e.g. the STS client of the web identity credentials provider) and
would add org.apache.hc.* classes that the shade configuration does
not relocate. It is excluded to keep the Apache 4 based HTTP stack.
Moving to apache5-client, as apache-client is deprecated since SDK
2.54.15, is left for later.
- NOTICE: the SDK versions are updated, and software.amazon.eventstream
is replaced by software.amazon.awssdk:eventstream because the SDK
dropped the former in 2.57.0 and ships its own module instead.
Generated-by: Claude Code 2.1.293 (Claude Sonnet 5.5)
* [FLINK-41014][s3] Address review feedback on the adaptive retry mode
- Shorten the description of s3.retry.mode and link the AWS SDK retry
guide. The README now says that the rate limiter exists per bucket and
TaskManager, as the factory builds one client provider per bucket.
- Log the resolved retry mode at INFO when the client provider is built.
- Test that the client of the provider is built with the strategy of the
configured mode and the configured number of attempts. The getters
checked so far did not notice a provider that ignores the mode.
- Assert the attempts of a failed request with S3Exception#numAttempts
instead of the SDK's message, and drop the assertion on the message of
the circuit breaker, as the SDK may reword both.
Generated-by: Claude Code 2.1.293 (Claude Sonnet 5.5)
---
flink-filesystems/flink-s3-fs-native/README.md | 3 +-
flink-filesystems/flink-s3-fs-native/pom.xml | 27 +++-
.../fs/s3native/NativeS3FileSystemFactory.java | 22 ++++
.../apache/flink/fs/s3native/S3ClientProvider.java | 41 ++++--
.../flink/fs/s3native/S3RetryStrategyFactory.java | 90 +++++++++++++
.../src/main/resources/META-INF/NOTICE | 68 +++++-----
.../fs/s3native/NativeS3FileSystemFactoryTest.java | 23 ++++
.../fs/s3native/S3ClientProviderRetryModeTest.java | 140 +++++++++++++++++++++
.../flink/fs/s3native/S3ClientProviderTest.java | 60 +++++++++
.../fs/s3native/S3RetryStrategyFactoryTest.java | 121 ++++++++++++++++++
.../flink-s3-fs-native/tools/download-crt-jars.sh | 2 +-
11 files changed, 546 insertions(+), 51 deletions(-)
diff --git a/flink-filesystems/flink-s3-fs-native/README.md
b/flink-filesystems/flink-s3-fs-native/README.md
index 4686186099e..c8c67493bdf 100644
--- a/flink-filesystems/flink-s3-fs-native/README.md
+++ b/flink-filesystems/flink-s3-fs-native/README.md
@@ -116,6 +116,7 @@ The plugin emits the following metric names:
| Key | Default | Description |
|-----|---------|-------------|
| s3.retry.max-num-retries | 3 | Maximum retry attempts for failed S3
requests. Uses the AWS SDK's default retry strategy (exponential backoff with
jitter). Set to `0` to disable retries |
+| s3.retry.mode | standard | `standard`: every request retries on its own,
which keeps all of them retrying at full speed against a request limit.
`adaptive`: additionally a client-side rate limiter, shared by all requests of
the S3 client (one per bucket and TaskManager), slows the requests down after
throttling answers (HTTP 429, S3 `SlowDown`) and lets them speed up after
successful ones, so that many parallel writers settle at the request rate the
object store accepts |
| s3.connection.timeout | 60s | HTTP connection timeout for the S3 client
(time to establish a connection) |
| s3.socket.timeout | 60s | HTTP socket timeout for the S3 client (time to
wait for data after connection is established) |
| s3.connection.max-idle-time | 60s | Maximum idle time for HTTP connections
in the connection pool |
@@ -443,7 +444,7 @@ If `mvn` is unavailable, fetch the JAR by hand from Maven
Central:
`aws-crt-<version>.jar` (groupId: `software.amazon.awssdk.crt`) — the version
is
declared as the `<dependency>` on `software.amazon.awssdk.crt:aws-crt` inside
`aws-crt-client-<version>.pom` on Maven Central. The `aws-crt` artifact uses an
-independent versioning scheme (e.g. `0.45.x`) that does **not** track the AWS
SDK
+independent versioning scheme (e.g. `0.48.x`) that does **not** track the AWS
SDK
version.
`aws-crt-client` does **not** need to be downloaded — it is bundled inside the
fat JAR.
diff --git a/flink-filesystems/flink-s3-fs-native/pom.xml
b/flink-filesystems/flink-s3-fs-native/pom.xml
index 1bdd1576b60..0e53bf8588c 100644
--- a/flink-filesystems/flink-s3-fs-native/pom.xml
+++ b/flink-filesystems/flink-s3-fs-native/pom.xml
@@ -31,11 +31,11 @@ under the License.
<packaging>jar</packaging>
<properties>
- <fs.s3.aws.sdk.version>2.44.4</fs.s3.aws.sdk.version>
+ <fs.s3.aws.sdk.version>2.57.2</fs.s3.aws.sdk.version>
<!-- aws-crt uses a separate versioning scheme from the AWS
SDK; this version was
validated against aws-crt-client ${fs.s3.aws.sdk.version}.
Update when bumping
the SDK version (check aws-crt-client-<version>.pom →
awscrt.version). -->
- <fs.s3.aws.crt.version>0.45.1</fs.s3.aws.crt.version>
+ <fs.s3.aws.crt.version>0.48.5</fs.s3.aws.crt.version>
<japicmp.skip>true</japicmp.skip>
<surefire.module.config>--add-opens=java.base/java.util=ALL-UNNAMED</surefire.module.config>
</properties>
@@ -55,11 +55,22 @@ under the License.
<scope>test</scope>
</dependency>
+ <!-- Since SDK 2.46.0 every service client pulls in
apache5-client at runtime, which makes it
+ the default HTTP client of clients built without an
explicit one (e.g. the STS client
+ of the web identity credentials provider) and bundles
org.apache.hc.*, which is not
+ relocated below. The connector uses apache-client
explicitly, so keep the HTTP stack
+ as it was and exclude apache5-client from every dependency
that brings it. -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>${fs.s3.aws.sdk.version}</version>
<optional>${flink.markBundledAsOptional}</optional>
+ <exclusions>
+ <exclusion>
+
<groupId>software.amazon.awssdk</groupId>
+ <artifactId>apache5-client</artifactId>
+ </exclusion>
+ </exclusions>
</dependency>
<!-- S3 Transfer Manager for async/high-throughput transfers -->
@@ -68,6 +79,12 @@ under the License.
<artifactId>s3-transfer-manager</artifactId>
<version>${fs.s3.aws.sdk.version}</version>
<optional>${flink.markBundledAsOptional}</optional>
+ <exclusions>
+ <exclusion>
+
<groupId>software.amazon.awssdk</groupId>
+ <artifactId>apache5-client</artifactId>
+ </exclusion>
+ </exclusions>
</dependency>
<dependency>
@@ -75,6 +92,12 @@ under the License.
<artifactId>sts</artifactId>
<version>${fs.s3.aws.sdk.version}</version>
<optional>${flink.markBundledAsOptional}</optional>
+ <exclusions>
+ <exclusion>
+
<groupId>software.amazon.awssdk</groupId>
+ <artifactId>apache5-client</artifactId>
+ </exclusion>
+ </exclusions>
</dependency>
<!-- Netty async HTTP client (required for Transfer Manager) -->
diff --git
a/flink-filesystems/flink-s3-fs-native/src/main/java/org/apache/flink/fs/s3native/NativeS3FileSystemFactory.java
b/flink-filesystems/flink-s3-fs-native/src/main/java/org/apache/flink/fs/s3native/NativeS3FileSystemFactory.java
index 55a48debfda..72639c145bd 100644
---
a/flink-filesystems/flink-s3-fs-native/src/main/java/org/apache/flink/fs/s3native/NativeS3FileSystemFactory.java
+++
b/flink-filesystems/flink-s3-fs-native/src/main/java/org/apache/flink/fs/s3native/NativeS3FileSystemFactory.java
@@ -312,6 +312,27 @@ public class NativeS3FileSystemFactory implements
FileSystemFactory, MetricsAwar
+ "configured backoff and retry count are
exhausted. Disabled by default "
+ "so the retry/backoff settings above
fully govern retry behavior.");
+ /** How the AWS SDK clients retry failed requests, see {@link
#RETRY_MODE}. */
+ public enum RetryMode {
+ STANDARD,
+ ADAPTIVE
+ }
+
+ public static final ConfigOption<RetryMode> RETRY_MODE =
+ ConfigOptions.key("s3.retry.mode")
+ .enumType(RetryMode.class)
+ .defaultValue(RetryMode.STANDARD)
+ .withDescription(
+ "How the AWS SDK retries failed requests.
'standard' (default): every "
+ + "request retries on its own, with the
delays set by the "
+ + "s3.retry.* options. 'adaptive': the
same delays, plus a "
+ + "client-side rate limiter that slows
requests down after "
+ + "throttling answers (HTTP 429, S3
SlowDown) and can delay a "
+ + "request before its first attempt. It
trades latency for "
+ + "success rate. The AWS CRT async client
(s3.crt.enabled) has "
+ + "retries of its own and ignores this
option. See "
+ +
"https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/retry-strategy.html");
+
public static final ConfigOption<Duration> CONNECTION_TIMEOUT =
ConfigOptions.key("s3.connection.timeout")
.durationType()
@@ -722,6 +743,7 @@ public class NativeS3FileSystemFactory implements
FileSystemFactory, MetricsAwar
.retryThrottleBaseDelay(config.get(RETRY_THROTTLE_BASE_DELAY))
.retryMaxBackoff(config.get(RETRY_MAX_BACKOFF))
.retryCircuitBreakerEnabled(config.get(RETRY_CIRCUIT_BREAKER_ENABLED))
+ .retryMode(config.get(RETRY_MODE))
.credentialsProviderClasses(credentialsProviderClasses)
.encryptionConfig(encryptionConfig)
.metricPublisher(resolveMetricBridge(config))
diff --git
a/flink-filesystems/flink-s3-fs-native/src/main/java/org/apache/flink/fs/s3native/S3ClientProvider.java
b/flink-filesystems/flink-s3-fs-native/src/main/java/org/apache/flink/fs/s3native/S3ClientProvider.java
index 634409da4d3..fe8153fd549 100644
---
a/flink-filesystems/flink-s3-fs-native/src/main/java/org/apache/flink/fs/s3native/S3ClientProvider.java
+++
b/flink-filesystems/flink-s3-fs-native/src/main/java/org/apache/flink/fs/s3native/S3ClientProvider.java
@@ -39,8 +39,6 @@ import
software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
import software.amazon.awssdk.metrics.MetricPublisher;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain;
-import software.amazon.awssdk.retries.StandardRetryStrategy;
-import software.amazon.awssdk.retries.api.BackoffStrategy;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3AsyncClientBuilder;
import software.amazon.awssdk.services.s3.S3Client;
@@ -99,6 +97,7 @@ class S3ClientProvider implements AutoCloseableAsync {
private final Duration retryThrottleBaseDelay;
private final Duration retryMaxBackoff;
private final boolean retryCircuitBreakerEnabled;
+ private final NativeS3FileSystemFactory.RetryMode retryMode;
@Nullable private final String region;
@Nullable private final String endpoint;
@Nullable private final String assumeRoleArn;
@@ -132,6 +131,7 @@ class S3ClientProvider implements AutoCloseableAsync {
Duration retryThrottleBaseDelay,
Duration retryMaxBackoff,
boolean retryCircuitBreakerEnabled,
+ NativeS3FileSystemFactory.RetryMode retryMode,
@Nullable String region,
@Nullable String endpoint,
@Nullable String assumeRoleArn,
@@ -176,6 +176,7 @@ class S3ClientProvider implements AutoCloseableAsync {
this.retryMaxBackoff =
Preconditions.checkNotNull(retryMaxBackoff, "retryMaxBackoff
must not be null");
this.retryCircuitBreakerEnabled = retryCircuitBreakerEnabled;
+ this.retryMode = Preconditions.checkNotNull(retryMode, "retryMode must
not be null");
this.region = region;
this.endpoint = endpoint;
this.assumeRoleArn = assumeRoleArn;
@@ -279,6 +280,11 @@ class S3ClientProvider implements AutoCloseableAsync {
return retryCircuitBreakerEnabled;
}
+ @VisibleForTesting
+ NativeS3FileSystemFactory.RetryMode getRetryMode() {
+ return retryMode;
+ }
+
@VisibleForTesting
@Nullable
String getRegion() {
@@ -438,6 +444,8 @@ class S3ClientProvider implements AutoCloseableAsync {
NativeS3FileSystemFactory.RETRY_MAX_BACKOFF.defaultValue();
private boolean retryCircuitBreakerEnabled =
NativeS3FileSystemFactory.RETRY_CIRCUIT_BREAKER_ENABLED.defaultValue();
+ private NativeS3FileSystemFactory.RetryMode retryMode =
+ NativeS3FileSystemFactory.RETRY_MODE.defaultValue();
private Duration clientCloseTimeout =
NativeS3FileSystemFactory.CLIENT_CLOSE_TIMEOUT.defaultValue();
@@ -570,6 +578,11 @@ class S3ClientProvider implements AutoCloseableAsync {
return this;
}
+ public Builder retryMode(NativeS3FileSystemFactory.RetryMode
retryMode) {
+ this.retryMode = Preconditions.checkNotNull(retryMode, "retryMode
must not be null");
+ return this;
+ }
+
public Builder assumeRoleArn(@Nullable String assumeRoleArn) {
this.assumeRoleArn = assumeRoleArn;
return this;
@@ -687,17 +700,18 @@ class S3ClientProvider implements AutoCloseableAsync {
ClientOverrideConfiguration.Builder overrideConfigBuilder =
ClientOverrideConfiguration.builder()
.retryStrategy(
- StandardRetryStrategy.builder()
- .maxAttempts(maxRetries + 1)
- .backoffStrategy(
-
BackoffStrategy.exponentialDelay(
- retryBaseDelay,
retryMaxBackoff))
- .throttlingBackoffStrategy(
-
BackoffStrategy.exponentialDelay(
-
retryThrottleBaseDelay,
- retryMaxBackoff))
-
.circuitBreakerEnabled(retryCircuitBreakerEnabled)
- .build());
+ S3RetryStrategyFactory.create(
+ retryMode,
+ maxRetries,
+ retryBaseDelay,
+ retryThrottleBaseDelay,
+ retryMaxBackoff,
+ retryCircuitBreakerEnabled));
+
+ LOG.info(
+ "S3 retry mode {} (s3.retry.mode) with up to {} retries
per request",
+ retryMode,
+ maxRetries);
if (useCrt) {
LOG.info(
@@ -742,6 +756,7 @@ class S3ClientProvider implements AutoCloseableAsync {
retryThrottleBaseDelay,
retryMaxBackoff,
retryCircuitBreakerEnabled,
+ retryMode,
region,
endpoint,
assumeRoleArn,
diff --git
a/flink-filesystems/flink-s3-fs-native/src/main/java/org/apache/flink/fs/s3native/S3RetryStrategyFactory.java
b/flink-filesystems/flink-s3-fs-native/src/main/java/org/apache/flink/fs/s3native/S3RetryStrategyFactory.java
new file mode 100644
index 00000000000..c911634b7fb
--- /dev/null
+++
b/flink-filesystems/flink-s3-fs-native/src/main/java/org/apache/flink/fs/s3native/S3RetryStrategyFactory.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.flink.fs.s3native;
+
+import org.apache.flink.annotation.Internal;
+
+import software.amazon.awssdk.retries.AdaptiveRetryStrategy;
+import software.amazon.awssdk.retries.StandardRetryStrategy;
+import software.amazon.awssdk.retries.api.BackoffStrategy;
+import software.amazon.awssdk.retries.api.RetryStrategy;
+import software.amazon.awssdk.retries.internal.DefaultAdaptiveRetryStrategy;
+
+import java.time.Duration;
+import java.util.Arrays;
+
+/**
+ * Builds the AWS SDK {@link RetryStrategy} used by the native S3 client's
sync/async clients: the
+ * standard or the adaptive one, see {@link
NativeS3FileSystemFactory#RETRY_MODE}. The delays are
+ * validated by {@link S3ClientProvider.Builder}.
+ */
+@Internal
+final class S3RetryStrategyFactory {
+
+ private S3RetryStrategyFactory() {}
+
+ static RetryStrategy create(
+ NativeS3FileSystemFactory.RetryMode retryMode,
+ int maxRetries,
+ Duration retryBaseDelay,
+ Duration retryThrottleBaseDelay,
+ Duration retryMaxBackoff,
+ boolean retryCircuitBreakerEnabled) {
+ BackoffStrategy backoff =
BackoffStrategy.exponentialDelay(retryBaseDelay, retryMaxBackoff);
+ BackoffStrategy throttlingBackoff =
+ BackoffStrategy.exponentialDelay(retryThrottleBaseDelay,
retryMaxBackoff);
+
+ switch (retryMode) {
+ case STANDARD:
+ return StandardRetryStrategy.builder()
+ .maxAttempts(maxRetries + 1)
+ .backoffStrategy(backoff)
+ .throttlingBackoffStrategy(throttlingBackoff)
+ .circuitBreakerEnabled(retryCircuitBreakerEnabled)
+ .build();
+ case ADAPTIVE:
+ return createAdaptive(
+ maxRetries, backoff, throttlingBackoff,
retryCircuitBreakerEnabled);
+ default:
+ throw new IllegalArgumentException(
+ "Unknown retry mode: "
+ + retryMode
+ + ". Supported values: "
+ +
Arrays.toString(NativeS3FileSystemFactory.RetryMode.values()));
+ }
+ }
+
+ private static RetryStrategy createAdaptive(
+ int maxRetries,
+ BackoffStrategy backoff,
+ BackoffStrategy throttlingBackoff,
+ boolean retryCircuitBreakerEnabled) {
+ AdaptiveRetryStrategy.Builder builder =
+ AdaptiveRetryStrategy.builder()
+ .maxAttempts(maxRetries + 1)
+ .backoffStrategy(backoff)
+ .throttlingBackoffStrategy(throttlingBackoff);
+ // The public builder of the adaptive strategy has no switch for the
circuit breaker, so it
+ // would stay on and a throttling storm would drain its token bucket
within seconds (see
+ // RETRY_CIRCUIT_BREAKER_ENABLED). The SDK's own builder has the
switch.
+ ((DefaultAdaptiveRetryStrategy.Builder) builder)
+ .circuitBreakerEnabled(retryCircuitBreakerEnabled);
+ return builder.build();
+ }
+}
diff --git
a/flink-filesystems/flink-s3-fs-native/src/main/resources/META-INF/NOTICE
b/flink-filesystems/flink-s3-fs-native/src/main/resources/META-INF/NOTICE
index 0f263329679..e61f5386348 100644
--- a/flink-filesystems/flink-s3-fs-native/src/main/resources/META-INF/NOTICE
+++ b/flink-filesystems/flink-s3-fs-native/src/main/resources/META-INF/NOTICE
@@ -6,40 +6,40 @@ The Apache Software Foundation (http://www.apache.org/).
This project bundles the following dependencies under the Apache Software
License 2.0 (http://www.apache.org/licenses/LICENSE-2.0.txt)
-- software.amazon.awssdk:annotations:2.44.4
-- software.amazon.awssdk:apache-client:2.44.4
-- software.amazon.awssdk:arns:2.44.4
-- software.amazon.awssdk:auth:2.44.4
-- software.amazon.awssdk:aws-core:2.44.4
-- software.amazon.awssdk:aws-crt-client:2.44.4
-- software.amazon.awssdk:aws-query-protocol:2.44.4
-- software.amazon.awssdk:aws-xml-protocol:2.44.4
-- software.amazon.awssdk:checksums:2.44.4
-- software.amazon.awssdk:checksums-spi:2.44.4
-- software.amazon.awssdk:crt-core:2.44.4
-- software.amazon.awssdk:endpoints-spi:2.44.4
-- software.amazon.awssdk:http-auth:2.44.4
-- software.amazon.awssdk:http-auth-aws:2.44.4
-- software.amazon.awssdk:http-auth-aws-eventstream:2.44.4
-- software.amazon.awssdk:http-auth-spi:2.44.4
-- software.amazon.awssdk:http-client-spi:2.44.4
-- software.amazon.awssdk:identity-spi:2.44.4
-- software.amazon.awssdk:json-utils:2.44.4
-- software.amazon.awssdk:metrics-spi:2.44.4
-- software.amazon.awssdk:netty-nio-client:2.44.4
-- software.amazon.awssdk:profiles:2.44.4
-- software.amazon.awssdk:protocol-core:2.44.4
-- software.amazon.awssdk:regions:2.44.4
-- software.amazon.awssdk:retries:2.44.4
-- software.amazon.awssdk:retries-spi:2.44.4
-- software.amazon.awssdk:s3:2.44.4
-- software.amazon.awssdk:s3-transfer-manager:2.44.4
-- software.amazon.awssdk:sdk-core:2.44.4
-- software.amazon.awssdk:sts:2.44.4
-- software.amazon.awssdk:third-party-jackson-core:2.44.4
-- software.amazon.awssdk:utils:2.44.4
-- software.amazon.awssdk:utils-lite:2.44.4
-- software.amazon.eventstream:eventstream:1.0.1
+- software.amazon.awssdk:annotations:2.57.2
+- software.amazon.awssdk:apache-client:2.57.2
+- software.amazon.awssdk:arns:2.57.2
+- software.amazon.awssdk:auth:2.57.2
+- software.amazon.awssdk:aws-core:2.57.2
+- software.amazon.awssdk:aws-crt-client:2.57.2
+- software.amazon.awssdk:aws-query-protocol:2.57.2
+- software.amazon.awssdk:aws-xml-protocol:2.57.2
+- software.amazon.awssdk:checksums:2.57.2
+- software.amazon.awssdk:checksums-spi:2.57.2
+- software.amazon.awssdk:crt-core:2.57.2
+- software.amazon.awssdk:endpoints-spi:2.57.2
+- software.amazon.awssdk:eventstream:2.57.2
+- software.amazon.awssdk:http-auth:2.57.2
+- software.amazon.awssdk:http-auth-aws:2.57.2
+- software.amazon.awssdk:http-auth-aws-eventstream:2.57.2
+- software.amazon.awssdk:http-auth-spi:2.57.2
+- software.amazon.awssdk:http-client-spi:2.57.2
+- software.amazon.awssdk:identity-spi:2.57.2
+- software.amazon.awssdk:json-utils:2.57.2
+- software.amazon.awssdk:metrics-spi:2.57.2
+- software.amazon.awssdk:netty-nio-client:2.57.2
+- software.amazon.awssdk:profiles:2.57.2
+- software.amazon.awssdk:protocol-core:2.57.2
+- software.amazon.awssdk:regions:2.57.2
+- software.amazon.awssdk:retries:2.57.2
+- software.amazon.awssdk:retries-spi:2.57.2
+- software.amazon.awssdk:s3:2.57.2
+- software.amazon.awssdk:s3-transfer-manager:2.57.2
+- software.amazon.awssdk:sdk-core:2.57.2
+- software.amazon.awssdk:sts:2.57.2
+- software.amazon.awssdk:third-party-jackson-core:2.57.2
+- software.amazon.awssdk:utils:2.57.2
+- software.amazon.awssdk:utils-lite:2.57.2
- org.apache.httpcomponents:httpclient:4.5.13
- org.apache.httpcomponents:httpcore:4.4.14
- commons-codec:commons-codec:1.15
diff --git
a/flink-filesystems/flink-s3-fs-native/src/test/java/org/apache/flink/fs/s3native/NativeS3FileSystemFactoryTest.java
b/flink-filesystems/flink-s3-fs-native/src/test/java/org/apache/flink/fs/s3native/NativeS3FileSystemFactoryTest.java
index 2d927d4fde7..9e208fff4b2 100644
---
a/flink-filesystems/flink-s3-fs-native/src/test/java/org/apache/flink/fs/s3native/NativeS3FileSystemFactoryTest.java
+++
b/flink-filesystems/flink-s3-fs-native/src/test/java/org/apache/flink/fs/s3native/NativeS3FileSystemFactoryTest.java
@@ -439,6 +439,29 @@ class NativeS3FileSystemFactoryTest {
assertThat(createFs(config).getClientProvider().isRetryCircuitBreakerEnabled()).isTrue();
}
+ @Test
+ void testRetryModeDefault() throws Exception {
+ assertThat(createFs(baseConfig()).getClientProvider().getRetryMode())
+ .isEqualTo(NativeS3FileSystemFactory.RetryMode.STANDARD);
+ }
+
+ @Test
+ void testRetryModeExplicitlyConfigured() throws Exception {
+ Configuration config = baseConfig();
+ config.set(
+ NativeS3FileSystemFactory.RETRY_MODE,
NativeS3FileSystemFactory.RetryMode.ADAPTIVE);
+ assertThat(createFs(config).getClientProvider().getRetryMode())
+ .isEqualTo(NativeS3FileSystemFactory.RetryMode.ADAPTIVE);
+ }
+
+ @Test
+ void testRetryModeIsReadFromTheLowerCaseValueOfAFlinkConfigFile() throws
Exception {
+ Configuration config = baseConfig();
+ config.setString("s3.retry.mode", "adaptive");
+ assertThat(createFs(config).getClientProvider().getRetryMode())
+ .isEqualTo(NativeS3FileSystemFactory.RetryMode.ADAPTIVE);
+ }
+
// --- Timeouts ---
@Test
diff --git
a/flink-filesystems/flink-s3-fs-native/src/test/java/org/apache/flink/fs/s3native/S3ClientProviderRetryModeTest.java
b/flink-filesystems/flink-s3-fs-native/src/test/java/org/apache/flink/fs/s3native/S3ClientProviderRetryModeTest.java
new file mode 100644
index 00000000000..840608f4c65
--- /dev/null
+++
b/flink-filesystems/flink-s3-fs-native/src/test/java/org/apache/flink/fs/s3native/S3ClientProviderRetryModeTest.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.flink.fs.s3native;
+
+import org.apache.flink.fs.s3native.NativeS3FileSystemFactory.RetryMode;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpServer;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import software.amazon.awssdk.core.ResponseBytes;
+import software.amazon.awssdk.services.s3.model.GetObjectRequest;
+import software.amazon.awssdk.services.s3.model.GetObjectResponse;
+import software.amazon.awssdk.services.s3.model.S3Exception;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Tests that a client built for either {@link RetryMode} retries what an
object store answers with
+ * SlowDown, against a local server that throttles a number of requests and
then answers.
+ */
+class S3ClientProviderRetryModeTest {
+
+ private static final String SLOW_DOWN =
+ "<Error><Code>SlowDown</Code><Message>Please reduce your request
rate.</Message>"
+ + "<RequestId>1</RequestId></Error>";
+
+ private final AtomicInteger requests = new AtomicInteger();
+
+ private HttpServer server;
+ private int throttledRequests;
+ private S3ClientProvider provider;
+
+ @BeforeEach
+ void startServer() throws IOException {
+ server = HttpServer.create(new
InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
+ server.createContext(
+ "/",
+ exchange -> {
+ if (requests.incrementAndGet() <= throttledRequests) {
+ respond(exchange, 503, SLOW_DOWN);
+ } else {
+ respond(exchange, 200, "hello");
+ }
+ });
+ server.start();
+ }
+
+ @AfterEach
+ void stopServer() throws Exception {
+ if (provider != null) {
+ provider.close();
+ }
+ server.stop(0);
+ }
+
+ @ParameterizedTest
+ @EnumSource(RetryMode.class)
+ void testAThrottledRequestIsRetriedUntilTheStoreAnswers(RetryMode mode) {
+ throttledRequests = 1;
+ provider = provider(mode, 5);
+
+ String body = getObject().asUtf8String();
+
+ assertThat(body).isEqualTo("hello");
+ assertThat(requests).hasValue(2);
+ }
+
+ @ParameterizedTest
+ @EnumSource(RetryMode.class)
+ void
testARequestThatStaysThrottledFailsAfterTheConfiguredRetries(RetryMode mode) {
+ throttledRequests = Integer.MAX_VALUE;
+ provider = provider(mode, 1);
+
+ assertThatThrownBy(this::getObject)
+ .isInstanceOfSatisfying(
+ S3Exception.class,
+ e -> {
+ assertThat(e.statusCode()).isEqualTo(503);
+ assertThat(e.numAttempts()).isEqualTo(2);
+ });
+ assertThat(requests).hasValue(2);
+ }
+
+ private ResponseBytes<GetObjectResponse> getObject() {
+ return provider.getS3Client()
+
.getObjectAsBytes(GetObjectRequest.builder().bucket("bucket").key("key").build());
+ }
+
+ private S3ClientProvider provider(RetryMode mode, int maxRetries) {
+ return S3ClientProvider.builder()
+ .endpoint("http://127.0.0.1:" + server.getAddress().getPort())
+ .region("us-east-1")
+ .pathStyleAccess(true)
+ .accessKey("access")
+ .secretKey("secret")
+ .retryMode(mode)
+ .maxRetries(maxRetries)
+ .retryBaseDelay(Duration.ofMillis(1))
+ .retryThrottleBaseDelay(Duration.ofMillis(1))
+ .retryMaxBackoff(Duration.ofMillis(10))
+ .build();
+ }
+
+ private static void respond(HttpExchange exchange, int status, String
body) throws IOException {
+ byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
+ exchange.getResponseHeaders().add("Content-Type", "application/xml");
+ exchange.sendResponseHeaders(status, bytes.length);
+ try (OutputStream out = exchange.getResponseBody()) {
+ out.write(bytes);
+ }
+ }
+}
diff --git
a/flink-filesystems/flink-s3-fs-native/src/test/java/org/apache/flink/fs/s3native/S3ClientProviderTest.java
b/flink-filesystems/flink-s3-fs-native/src/test/java/org/apache/flink/fs/s3native/S3ClientProviderTest.java
index 935960837a3..eeec85d641b 100644
---
a/flink-filesystems/flink-s3-fs-native/src/test/java/org/apache/flink/fs/s3native/S3ClientProviderTest.java
+++
b/flink-filesystems/flink-s3-fs-native/src/test/java/org/apache/flink/fs/s3native/S3ClientProviderTest.java
@@ -28,6 +28,9 @@ import software.amazon.awssdk.auth.credentials.AwsCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
+import software.amazon.awssdk.retries.AdaptiveRetryStrategy;
+import software.amazon.awssdk.retries.StandardRetryStrategy;
+import software.amazon.awssdk.retries.api.RetryStrategy;
import
software.amazon.awssdk.services.sts.auth.StsAssumeRoleCredentialsProvider;
import java.lang.reflect.Field;
@@ -239,6 +242,54 @@ class S3ClientProviderTest {
.isEqualTo(NativeS3FileSystemFactory.RETRY_MAX_BACKOFF.defaultValue());
assertThat(provider.isRetryCircuitBreakerEnabled())
.isEqualTo(NativeS3FileSystemFactory.RETRY_CIRCUIT_BREAKER_ENABLED.defaultValue());
+ assertThat(provider.getRetryMode())
+
.isEqualTo(NativeS3FileSystemFactory.RETRY_MODE.defaultValue());
+ }
+
+ @Test
+ void testRetryModeOverride() {
+ S3ClientProvider provider =
+ S3ClientProvider.builder()
+ .endpoint(DUMMY_ENDPOINT)
+ .region(DUMMY_REGION)
+
.retryMode(NativeS3FileSystemFactory.RetryMode.ADAPTIVE)
+ .build();
+
+
assertThat(provider.getRetryMode()).isEqualTo(NativeS3FileSystemFactory.RetryMode.ADAPTIVE);
+ }
+
+ @Test
+ void testStandardModeBuildsAClientWithTheStandardStrategy() {
+ S3ClientProvider provider =
+ track(
+ S3ClientProvider.builder()
+ .endpoint(DUMMY_ENDPOINT)
+ .region(DUMMY_REGION)
+
.retryMode(NativeS3FileSystemFactory.RetryMode.STANDARD)
+ .maxRetries(5)
+ .build());
+
+ RetryStrategy strategy = retryStrategyOf(provider);
+
+ assertThat(strategy).isInstanceOf(StandardRetryStrategy.class);
+ assertThat(strategy.maxAttempts()).isEqualTo(6);
+ }
+
+ @Test
+ void testAdaptiveModeBuildsAClientWithTheAdaptiveStrategy() {
+ S3ClientProvider provider =
+ track(
+ S3ClientProvider.builder()
+ .endpoint(DUMMY_ENDPOINT)
+ .region(DUMMY_REGION)
+
.retryMode(NativeS3FileSystemFactory.RetryMode.ADAPTIVE)
+ .maxRetries(5)
+ .build());
+
+ RetryStrategy strategy = retryStrategyOf(provider);
+
+ assertThat(strategy).isInstanceOf(AdaptiveRetryStrategy.class);
+ assertThat(strategy.maxAttempts()).isEqualTo(6);
}
@Test
@@ -382,4 +433,13 @@ class S3ClientProviderTest {
field.setAccessible(true);
return (List<AwsCredentialsProvider>) field.get(provider);
}
+
+ /** The retry strategy the SDK client of the provider was built with. */
+ private static RetryStrategy retryStrategyOf(S3ClientProvider provider) {
+ return provider.getS3Client()
+ .serviceClientConfiguration()
+ .overrideConfiguration()
+ .retryStrategy()
+ .orElseThrow(() -> new AssertionError("The client has no retry
strategy"));
+ }
}
diff --git
a/flink-filesystems/flink-s3-fs-native/src/test/java/org/apache/flink/fs/s3native/S3RetryStrategyFactoryTest.java
b/flink-filesystems/flink-s3-fs-native/src/test/java/org/apache/flink/fs/s3native/S3RetryStrategyFactoryTest.java
new file mode 100644
index 00000000000..eae16eda5ee
--- /dev/null
+++
b/flink-filesystems/flink-s3-fs-native/src/test/java/org/apache/flink/fs/s3native/S3RetryStrategyFactoryTest.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.flink.fs.s3native;
+
+import org.apache.flink.fs.s3native.NativeS3FileSystemFactory.RetryMode;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import software.amazon.awssdk.core.exception.SdkClientException;
+import software.amazon.awssdk.retries.AdaptiveRetryStrategy;
+import software.amazon.awssdk.retries.StandardRetryStrategy;
+import software.amazon.awssdk.retries.api.AcquireInitialTokenRequest;
+import software.amazon.awssdk.retries.api.RefreshRetryTokenRequest;
+import software.amazon.awssdk.retries.api.RetryStrategy;
+import software.amazon.awssdk.retries.api.RetryToken;
+import software.amazon.awssdk.retries.api.TokenAcquisitionFailedException;
+
+import java.time.Duration;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/** Tests for {@link S3RetryStrategyFactory}. */
+class S3RetryStrategyFactoryTest {
+
+ private static RetryStrategy create(RetryMode mode, boolean
circuitBreakerEnabled) {
+ return S3RetryStrategyFactory.create(
+ mode,
+ 10,
+ Duration.ofMillis(100),
+ Duration.ofSeconds(1),
+ Duration.ofSeconds(20),
+ circuitBreakerEnabled);
+ }
+
+ /**
+ * The strategy as the SDK client completes it: the client adds the
conditions that say what is
+ * retried, here everything.
+ */
+ private static RetryStrategy retryingEverything(RetryStrategy strategy) {
+ RetryStrategy.Builder<?, ?> builder = strategy.toBuilder();
+ builder.retryOnException(failure -> true);
+ return builder.build();
+ }
+
+ /** One call whose attempts fail up to the last one: all ten retries of
the strategy. */
+ private static void retryTenTimes(RetryStrategy strategy) {
+ SdkClientException failure = SdkClientException.create("boom");
+ RetryToken token =
+
strategy.acquireInitialToken(AcquireInitialTokenRequest.create("test")).token();
+ for (int retry = 0; retry < 10; retry++) {
+ token =
+ strategy.refreshRetryToken(
+ RefreshRetryTokenRequest.builder()
+ .token(token)
+ .failure(failure)
+ .build())
+ .token();
+ }
+ }
+
+ @Test
+ void testStandardModeBuildsTheStandardStrategy() {
+ assertThat(create(RetryMode.STANDARD,
false)).isInstanceOf(StandardRetryStrategy.class);
+ }
+
+ @Test
+ void testAdaptiveModeBuildsTheAdaptiveStrategy() {
+ assertThat(create(RetryMode.ADAPTIVE,
false)).isInstanceOf(AdaptiveRetryStrategy.class);
+ }
+
+ @ParameterizedTest
+ @EnumSource(RetryMode.class)
+ void testMaxAttemptsIsMaxRetriesPlusOne(RetryMode mode) {
+ assertThat(create(mode, false).maxAttempts()).isEqualTo(11);
+ }
+
+ @ParameterizedTest
+ @EnumSource(RetryMode.class)
+ void testCircuitBreakerStaysOffUnlessEnabled(RetryMode mode) {
+ RetryStrategy strategy = retryingEverything(create(mode, false));
+
+ // Ten retries cost 50 tokens of the 500 of the breaker: it would be
empty after ten calls.
+ for (int call = 0; call < 30; call++) {
+ retryTenTimes(strategy);
+ }
+ }
+
+ @ParameterizedTest
+ @EnumSource(RetryMode.class)
+ void testCircuitBreakerStopsTheRetriesWhenEnabled(RetryMode mode) {
+ RetryStrategy strategy = retryingEverything(create(mode, true));
+
+ // A call never exceeds its attempts (ten retries of the ten allowed)
and the same calls
+ // pass with the breaker off, so only the drained breaker can fail
them.
+ assertThatThrownBy(
+ () -> {
+ for (int call = 0; call < 30; call++) {
+ retryTenTimes(strategy);
+ }
+ })
+ .isInstanceOf(TokenAcquisitionFailedException.class);
+ }
+}
diff --git a/flink-filesystems/flink-s3-fs-native/tools/download-crt-jars.sh
b/flink-filesystems/flink-s3-fs-native/tools/download-crt-jars.sh
index d3e29fbe708..a0974bb2309 100755
--- a/flink-filesystems/flink-s3-fs-native/tools/download-crt-jars.sh
+++ b/flink-filesystems/flink-s3-fs-native/tools/download-crt-jars.sh
@@ -110,7 +110,7 @@ CRT_VERSION="$(resolve_crt_version || true)"
if [[ -z "${CRT_VERSION}" ]]; then
echo "ERROR: aws-crt version could not be resolved for
aws-crt-client:${SDK_VERSION}." >&2
echo " Set AWS_CRT_VERSION explicitly and re-run, e.g." >&2
- echo " AWS_CRT_VERSION=0.33.6 $0 ${OUTPUT_DIR}" >&2
+ echo " AWS_CRT_VERSION=<version> $0 ${OUTPUT_DIR}" >&2
exit 1
fi