Repository navigation
[DDB Mapper V2] Add UserAgent - #7480
Merged
alextwoods merged 9 commits intoOct 23, 2026
Merged
Conversation
The latest master merge bumped the tree to 2.55.14-SNAPSHOT, but the mapper pom was left on 2.55.5-SNAPSHOT, so no module in the reactor could be built (Non-resolvable import POM bom-internal 2.55.5-SNAPSHOT). Mirrors the earlier fix in #7398. Verified: mvn -o validate -pl :dynamodb-mapper,:sdk-core and mvn clean install -pl :dynamodb-mapper -P quick --am both BUILD SUCCESS.
Sync the already-reserved "Ac" feature ID (DYNAMODB_MAPPER_V2) from the
AwsDrSeps FEATURES.md source of truth. It identifies operations called
through the v2 DynamoDB mapper and is distinct from the enhanced
client's DDB_MAPPER ("d"). VALUE_MAP is case-sensitive, so "Ac" does
not collide with CREDENTIALS_PROFILE_LOGIN ("AC").
Verified:
- mvn clean install -pl :sdk-core -P quick: BUILD SUCCESS
- env -u AWS_SDK_UA_APP_ID mvn clean install -pl :sdk-core: BUILD
SUCCESS (1618 tests, 0 failures, 6 skipped; 0 checkstyle; 0 spotbugs).
AWS_SDK_UA_APP_ID is unset because the IDE shell exports it, which
breaks 3 AppIdResolutionTest cases independent of this change.
Port the enhanced client's ApplyUserAgentInterceptor to the v2 mapper.
Its modifyRequest adds an "sdk-metrics"/"Ac" ApiName to the override
configuration of every DynamoDbRequest, preserving any existing
override configuration, so mapper usage shows up as the
DYNAMODB_MAPPER_V2 business metric rather than the enhanced client's
DDB_MAPPER ("d").
The interceptor is registered through the classpath resource
software/amazon/awssdk/services/dynamodb/execution.interceptors, like
the enhanced client, because the mapper always receives a user-built
immutable DynamoDbClient. A GraalVM reflect-config.json mirrors the
enhanced module. Caveat: as with the enhanced client, this tags every
DynamoDbRequest sent by any DynamoDbClient in a JVM that has the
dynamodb-mapper jar on its classpath, mapper-issued or not. Shaded
fat jars need an AppendingTransformer for that resource path.
Tests:
- ApplyUserAgentInterceptorTest (3): adds Ac (not d), preserves an
existing ApiName and header, leaves non-DynamoDB requests untouched.
- DynamoDBMapperUserAgentTest (18): builds a DynamoDbClient with no
explicit interceptor and a capturing SdkHttpClient, runs every mapper
operation family (load, save update/put, delete, query/queryPage/
next page/count, scan/scanPage/next page/count, parallelScan,
batchLoad, batchSave, transactionWrite, transactionLoad, table
mapper load) and asserts every request's User-Agent m/ section
contains Ac and not d.
Verified:
- mvn clean install -pl :dynamodb-mapper (non-quick): BUILD SUCCESS,
748 tests, 0 failures; spotbugs 0 bugs; dependency analyze-only
clean.
- mvn javadoc:javadoc -pl :dynamodb-mapper: 0 errors.
- Installed jar contains the interceptor class, execution.interceptors
and reflect-config.json; reflect-config.json is valid JSON.
- Negative check: with execution.interceptors emptied,
DynamoDBMapperUserAgentTest fails 18/18 (User-Agent had m/D,AK,P,e
and no Ac); file restored afterwards.
dynamodb-enhanced and dynamodb-mapper both ship software/amazon/awssdk/services/dynamodb/execution.interceptors. The benchmarks uber-jar had no AppendingTransformer for that path, so maven-shade kept only one file (the mapper's, declared first) and silently dropped the enhanced client's interceptor, which would skew any before/after comparison. Mirrors test/s3-benchmarks. Verified: mvn clean install -pl :sdk-benchmarks -P quick --am is BUILD SUCCESS, and the benchmarks.jar entry now lists both the mapper and enhanced ApplyUserAgentInterceptor on separate lines (shade inserts a blank line between them, which ClasspathInterceptorChainFactory skips).
The V1 mapper comparison benchmarks drive the v2 mapper through V2TestDynamoDb*Client stubs that implement DynamoDbClient directly, so the client pipeline and its classpath execution interceptors never run and cannot show the cost of the mapper's ApplyUserAgentInterceptor. - V2MapperRealClientOverheadBenchmark: v2 mapper load/save(PUT) through a real DynamoDbClient backed by MockHttpClient, for the TINY/SMALL/HUGE/HUGE_FLAT items. - V2MapperApplyUserAgentInterceptorBenchmark: direct cost of modifyRequest on GetItem and PutItem requests of several sizes, with and without a pre-existing override configuration, against a no-op baseline. Verified: mvn clean install -pl :sdk-benchmarks (checkstyle, spotbugs) BUILD SUCCESS; both benchmarks run from target/benchmarks.jar.
ClasspathInterceptorChainFactory de-duplicates classpath interceptors by simple class name. The mapper's interceptor shared the simple name ApplyUserAgentInterceptor with the enhanced client's, so with both jars on the classpath only one survived and either the mapper's Ac metric or the enhanced client's d metric was silently dropped. Rename it to ApplyMapperUserAgentInterceptor and assert in DynamoDBMapperUserAgentTest that the enhanced client interceptor on the test classpath still runs alongside it.
Replace the classpath execution interceptor with per-request tagging, ported from v1's DynamoDBMapper.applyUserAgent. The new internal MapperUserAgent.applyUserAgent sets one shared override configuration carrying the sdk-metrics/Ac API name on each request builder the mapper creates, before build(). This addresses two review findings on the interceptor approach: - Bundle shading collision: dynamodb-enhanced and dynamodb-mapper both shipped services/dynamodb/execution.interceptors. The bundle jar and customer uber-jars keep only one copy, so one of the d and Ac metrics was silently lost. The mapper no longer ships that resource. - Over-attribution: a classpath interceptor tagged every DynamoDB client request in the JVM, including direct low-level, async and enhanced client calls. Now only requests the mapper sends carry Ac. Batch and transaction requests carry Ac too. v1 used separate user agent strings for them, but only one feature ID is reserved. Every client call site is tagged: get, put, update, delete, batch write (each unprocessed-items retry builds a tagged request), batch get, transact write/get, and the scan/query request factories. Later pages, count loops, parallel scan segments and batch get retries keep the tag through toBuilder(). The public PaginatedQueryList and PaginatedScanList, and ParallelScanTask, also tag each next-page builder, as v1 did, because their constructors accept requests the mapper did not build. Remove the interceptor, its execution.interceptors and reflect-config resources, its unit test, and the V2MapperApplyUserAgentInterceptor micro-benchmark that depends on it. DynamoDBMapperUserAgentTest now also covers unprocessed-item retries for batch write and batch get, multi-page count and parallel scan, and batch delete. It adds a negative test that direct calls on a client shared with the mapper do not carry Ac, and drops the hll/ddb-enh assertion that only guarded the interceptor name collision.
Revert the AppendingTransformer for services/dynamodb/execution.interceptors added in 5e9840f. It worked around the bundle shading collision for the benchmarks jar only. The mapper now tags its own requests instead of shipping that resource, so dynamodb-enhanced is again its only provider and the default shade behavior keeps it. Update the V2MapperRealClientOverheadBenchmark javadoc, which still described the removed classpath interceptor.
Per-request tagging replaced the classpath interceptor to fix the bundle shading collision and the over-attribution of non-mapper requests. Its helper replaced the whole request override configuration, though. PaginatedQueryList and PaginatedScanList are public and accept caller-built requests, so a caller's override (credentials provider, timeouts, headers, API names) was dropped from page 2 onward. applyUserAgent now merges, as v1 did: - no override: set the shared override, so the common path copies nothing; - override already has sdk-metrics/Ac: leave it unchanged, so pages derived from each other don't accumulate one Ac per page; - otherwise: add the API name to a copy of the existing override. Add tests that caller-built paginated lists keep the caller's override on every later page and carry Ac exactly once.
dagnir
approved these changes
Oct 23, 2026
|
This pull request has been closed and the conversation has been locked. Comments on closed PRs are hard for our team to see. If you need more assistance, please open a new issue that references this one. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note: This PR merges to the DDB MapperV2 feature branch and NOT to master!
Motivation and Context
This PR adds user agent tracking for the v2 DynamoDB mapper using the business metrics feature. Every request the mapper sends is tagged with the reserved
DYNAMODB_MAPPER_V2(Ac) feature ID. This keeps v2 mapper usage separate from the Enhanced Client's (DDB_MAPPER/d).Modifications
Ac) to BusinessMetricFeatureIdMapperUserAgent.applyUserAgent(builder), which addsApiName("sdk-metrics", "Ac")to each request builder before the mapper builds and sends the request. This ports v1'sDynamoDBMapper.applyUserAgent. It is applied at all 17 client call sites inDynamoDBMapper,PaginatedQueryList,PaginatedScanListandParallelScanTask. That covers every query/scan page, parallel scan segments, count loops, transactions, and batch retries for unprocessed items/keys.Testing
Adds new unit tests to cover.
Also adds benchmarking to confirm the overhead of the interceptor is minimal. See results below.
Benchmark results
There is no overhead above measurement noise. For requests the mapper builds itself, the helper does one null check and sets one shared reference on the request builder. There is no request copy.
V2MapperRealClientOverheadBenchmarkdrives the mapper'sload(GetItem) andsave(PutItem) through a realDynamoDbClientbacked byMockHttpClient, so the full client pipeline runs but there is no network I/O. Arms: with = this PR; none = the same jar withapplyUserAgentreplaced by a no-op. Each arm was verified before timing (Acon mapper requests only in "with", absent in "none"). JMH-f 1 -wi 3 -i 5 -w 1s -r 2s, 3 interleaved repetitions per arm, Apple M4 Pro, JDK 21.Types of changes
License