Summary
When DynamoDbClientBuilder.endpointOverride(uri) is configured, the resolved endpoint is static for the lifetime of the client. However, DynamoDbResolveEndpointInterceptor.modifyRequest() still calls DefaultDynamoDbEndpointProvider.resolveEndpoint() on every single request, which re-evaluates the full endpoint rules engine and re-parses the URI from scratch each time.
Evidence
Using async-profiler on a JanusGraph server with a DynamoDB-heavy read workload (Query-dominated, ~thousands of DynamoDB calls/sec, SDK version 2.42.28):
DynamoDbResolveEndpointInterceptor.modifyRequest: 1.97% cumulative CPU of total JVM CPU
java/net/URI$Parser.scan: 0.36% flat CPU — URI re-parsed on every request
DefaultDynamoDbEndpointProvider.resolveEndpoint() runs the full rules engine even when the endpoint was statically configured
This is a subset of the broader AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext overhead (6.44% total).
Workaround
Bypassing endpointOverride in favor of a custom endpointProvider lambda that returns a pre-built static Endpoint eliminates this per-request cost entirely:
URI resolvedEndpoint = URI.create("https://dynamodb.us-east-1.amazonaws.com");
Endpoint staticEndpoint = Endpoint.builder().url(resolvedEndpoint).build();
DynamoDbClient.builder()
.endpointProvider(params -> CompletableFuture.completedFuture(staticEndpoint))
// ... rest of config
.build();
This saves ~0.5% CPU on our workload. The workaround is not obvious and shouldn't be required — endpointOverride is a documented, common configuration pattern.
Proposed fix
When endpointOverride is set at client construction time, the DynamoDbResolveEndpointInterceptor (or the underlying DefaultDynamoDbEndpointProvider) should cache the resolved Endpoint after the first evaluation and return the cached result on subsequent requests. Since the endpoint override URI is immutable after construction, this is safe.
Alternatively, the interceptor could detect at construction time that endpointOverride is present and substitute a no-op/cached implementation.
Relationship to PR #6820
PR #6820 ("Move endpoint resolution from interceptors to pipeline stage") addresses this architecturally. This issue is filed to track the specific user-visible problem — static endpointOverride causing per-request endpoint rules evaluation — in case the PR takes time to land or a targeted fix is preferred.
Summary
When
DynamoDbClientBuilder.endpointOverride(uri)is configured, the resolved endpoint is static for the lifetime of the client. However,DynamoDbResolveEndpointInterceptor.modifyRequest()still callsDefaultDynamoDbEndpointProvider.resolveEndpoint()on every single request, which re-evaluates the full endpoint rules engine and re-parses the URI from scratch each time.Evidence
Using async-profiler on a JanusGraph server with a DynamoDB-heavy read workload (Query-dominated, ~thousands of DynamoDB calls/sec, SDK version 2.42.28):
DynamoDbResolveEndpointInterceptor.modifyRequest: 1.97% cumulative CPU of total JVM CPUjava/net/URI$Parser.scan: 0.36% flat CPU — URI re-parsed on every requestDefaultDynamoDbEndpointProvider.resolveEndpoint()runs the full rules engine even when the endpoint was statically configuredThis is a subset of the broader
AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContextoverhead (6.44% total).Workaround
Bypassing
endpointOverridein favor of a customendpointProviderlambda that returns a pre-built staticEndpointeliminates this per-request cost entirely:This saves ~0.5% CPU on our workload. The workaround is not obvious and shouldn't be required —
endpointOverrideis a documented, common configuration pattern.Proposed fix
When
endpointOverrideis set at client construction time, theDynamoDbResolveEndpointInterceptor(or the underlyingDefaultDynamoDbEndpointProvider) should cache the resolvedEndpointafter the first evaluation and return the cached result on subsequent requests. Since the endpoint override URI is immutable after construction, this is safe.Alternatively, the interceptor could detect at construction time that
endpointOverrideis present and substitute a no-op/cached implementation.Relationship to PR #6820
PR #6820 ("Move endpoint resolution from interceptors to pipeline stage") addresses this architecturally. This issue is filed to track the specific user-visible problem — static
endpointOverridecausing per-request endpoint rules evaluation — in case the PR takes time to land or a targeted fix is preferred.