Skip to content

feat: Add operator access to configure AWS retry policy. Default to STANDARD, recommended by AWS - #19892

Open
capistrant wants to merge 10 commits into
apache:masterfrom
capistrant:s3-client-retry-policy-configurability
Open

feat: Add operator access to configure AWS retry policy. Default to STANDARD, recommended by AWS#19892
capistrant wants to merge 10 commits into
apache:masterfrom
capistrant:s3-client-retry-policy-configurability

Conversation

@capistrant

@capistrant capistrant commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Description

AWS sdk has made changes to how s3 retries are handled by the client. There are multiple policy decisions available. https://docs.aws.amazon.com/sdkref/latest/guide/feature-retry-behavior.html can be referenced for more information. The biggest change in this PR is ensuring that we are operating with STANDARD which is the general recommendation for policy going forward. ADAPTIVE is the special case policy that I want to expose to operators. We do update docs to suggest that ingestion tasks may be good fits for adaptive but we do not go as far as changing the policy for anything to use adaptive by default. If we find that it is far and away better, perhaps future releases will create clients with policies tailored to the job of the service.

Edit: Previously the PR gave guidance on retry mode selection per druid service type. On advice of review, we have scaled the docs way back to simply expose the new runtime property and link to the AWS docs on choosing a retry mode. I was overselling confidence in what mode is good for what service. If, in the future, we determine adaptive is better than standard for anything we will opt into that by default.

Release note

Explicitly change to the AWS Standard retry policy for the s3 client. The retry mode used by s3 client is configurable now in case operators want to customize. One such customization that may be useful is moving druid.s3.retryMode to adaptive for ingestion tasks. Operators should use caution when making changes the the retry mode, and consult https://docs.aws.amazon.com/sdkref/latest/guide/feature-retry-behavior.html before making any decisions for critical service configuration.


Key changed/added classes in this PR
  • AWSClientConfig
  • ServerSideEncryptingAmazonS3

This PR has:

  • been self-reviewed.
  • added documentation for new or modified features or behaviors.
  • a release note entry in the PR description.
  • added Javadocs for most classes and all non-trivial methods. Linked related entities via Javadoc links.
  • added or updated version, license, or notice information in licenses.yaml
  • added comments explaining the "why" and the intent of the code wherever would not be obvious for an unfamiliar reader.
  • added unit tests or modified existing tests to cover new code paths, ensuring the threshold for code coverage is met.
  • added integration tests.
  • been tested in a test Druid cluster.

AWSClientConfig config = mapperWithRuntimeInfo(new FixedProcessorsRuntimeInfo(32))
.readValue("{}", AWSClientConfig.class);
Assertions.assertEquals(128, config.getMaxConnections());
Assertions.assertNull(bind(Map.of()).isForceGlobalBucketAccessEnabled());
.readValue("{}", AWSClientConfig.class);
Assertions.assertEquals(128, config.getMaxConnections());
Assertions.assertNull(bind(Map.of()).isForceGlobalBucketAccessEnabled());
Assertions.assertNull(bind(Map.of("crossRegionAccessEnabled", true)).isForceGlobalBucketAccessEnabled());
private static final int DEFAULT_MAX_CONNECTIONS_FLOOR = 50;

/**
* Selects the retry behavior AWS documents for {@code standard} and {@code adaptive} rather than the pre-2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found this javadoc hard to understand until reading https://docs.aws.amazon.com/sdkref/latest/guide/feature-retry-behavior.html. It would be helpful to reword the javadoc, or link to https://docs.aws.amazon.com/sdkref/latest/guide/feature-retry-behavior.html, or ideally.

I think it would also be fine to get rid of this constant and instead inline true into the calls to standardRetryStrategy and adaptiveRetryStrategy. Sometimes less is more.

private RetryMode retryMode = RetryMode.STANDARD;

/**
* Total attempts per request, including the first. Maps directly to the SDK's {@code maxAttempts}, so 1 disables

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If 1 disables retries then this config should be named maxAttempts, not maxRetryAttempts. (1 retry attempt means there were 2 attempts: an initial attempt and a retry attempt).

Comment thread docs/development/extensions-core/s3.md Outdated
|`druid.storage.sse.kms.keyId`|AWS KMS key ID. This is used only when `druid.storage.sse.type` is `kms` and can be empty to use the default key ID.|None|
|`druid.storage.sse.custom.base64EncodedKey`|Base64-encoded key. Should be specified if `druid.storage.sse.type` is `custom`.|None|

## Retry behavior

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread docs/development/extensions-core/s3.md Outdated
## Retry behavior

Druid names the AWS SDK retry strategy explicitly rather than accepting the SDK's default. The SDK's own default
depends on the `aws.newRetries2026` migration flag, so leaving it unset would let retry behavior change underneath

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think mentioning this flag or the SDK defaults here is useful, because neither of them matter (since we're explicitly creating policies such that the flag and SDK defaults are ignored). Bringing them up makes it seem like something the operator might need to care about.

Comment thread docs/development/extensions-core/s3.md Outdated
|`adaptive`|`standard` plus a client-side rate limiter that slows requests down when S3 reports throttling. Unlike `standard`, it can delay or block the **initial** request, not only retries. The limiter covers every request made by one client instance, so throttling on one key prefix also slows requests to prefixes that are not being throttled.|
|`legacy`|The SDK's legacy behavior, retained so a deployment can revert without a rollback. AWS recommends moving off it.|

### Choosing a mode

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this section should be here. If we really think that these suggestions are good then they should be implemented as defaults in code.

Comment thread docs/development/extensions-core/s3.md Outdated
a Kubernetes pod template, or `druid.indexer.fork.property.druid.s3.retryMode` in a task's context when using the
MiddleManager task runner.

|Process|Suggested mode|Reasoning|

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we can make such specific claims about how prefixes work with rate limiting. The AWS docs, to me, seem vague as to what exactly the rate-limitable prefix is. They don't appear to promise that it's any particular prefix of the key. The page at https://repost.aws/knowledge-center/s3-prefix-nested-folders-difference suggests that AWS partitions the prefixes adaptively in some way that is possibly opaque to the user.

Anyway, we won't need to worry about this if we remove the subsection as I am also suggesting.

Comment thread docs/development/extensions-core/s3.md Outdated
|Historicals|`standard`|One client loads segments for every datasource the process serves, so a rate limiter tripped by one prefix would slow loads for unrelated ones. Segment loads can also sit on the query path.|
|Brokers, Coordinator, Overlord|`standard`|Low request volume, and delaying an initial request costs query latency for no benefit.|

### Retry quota

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe better to link to https://docs.aws.amazon.com/sdkref/latest/guide/feature-retry-behavior.html vs trying to summarize it.

Comment thread docs/development/extensions-core/s3.md Outdated
|Layer|Scope of one attempt|Attempts|Backoff cap|
|-----|--------------------|--------|-----------|
|`druid.s3.maxRetryAttempts`|A single HTTP request, such as one `UploadPart`|Set by the retry mode|`~20s`|
|`S3Utils.retryS3Operation`|A whole logical operation, such as re-uploading an entire segment|10|`60s`|

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Operator docs shouldn't refer to Java classes/functions such as S3Utils.retryS3Operation.

@FrankChen021 FrankChen021 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity Findings
P0 0
P1 2
P2 0
P3 0
Total 2

Reviewed 7 of 7 changed files. Retry state is shared across two clients, and custom input clients can bypass the process retry configuration.


This is an automated review by Codex GPT-5.6-Sol

S3Configuration s3Config = S3Configuration.builder()
.chunkedEncodingEnabled(!awsClientConfig.isDisableChunkedEncoding())
.build();
final ClientOverrideConfiguration retryOverrides =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Give each S3 client its own retry strategy

This override captures one stateful RetryStrategy and hands the same instance to both the synchronous and asynchronous clients. The SDK's standard strategy stores circuit-breaker quota on the strategy instance, while adaptive additionally stores its rate limiter there, using a global scope. Consequently, throttled TransferManager uploads can drain retry quota or delay synchronous reads and listings, contrary to the documented per-client isolation. Build separate override configurations by calling getRetryStrategy() independently for each client.

if (awsEndpointConfig != null && awsEndpointConfig.getSigningRegion() != null) {
stsBuilder.region(Region.of(awsEndpointConfig.getSigningRegion()));
}
if (awsClientConfig != null) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Preserve global retry settings for custom input clients

Here and in the S3 setup above, null leaves the SDK policy unchanged. For S3InputSource this argument is the nullable per-spec clientConfig, not the Guice-bound process configuration. An ingestion spec that supplies properties or assumeRoleArn but omits clientConfig therefore builds new S3 and STS clients that silently ignore druid.s3.retryMode and maxRetryAttempts, including the task-context configuration recommended by the new documentation. Fall back to the injected process AWSClientConfig when no per-source override is provided and cover this path in a test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

regarding S3InputSource... I actually don't want to be adding that to scope in this PR. the docs explicitly talking so confidently about this config for tasks was a mistake and has been reverted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants