From 4e9e1de4b2146675b4f3ffe52fbdc389ba6c5733 Mon Sep 17 00:00:00 2001 From: RanVaknin <50976344+RanVaknin@users.noreply.github.com> Date: Thu, 5 Feb 2026 11:39:32 -0800 Subject: [PATCH 1/3] Added cache for computation of partition keys --- .../enhanced/dynamodb/mapper/StaticTableMetadata.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableMetadata.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableMetadata.java index 2126f25dec51..4bdb79d378e6 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableMetadata.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableMetadata.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import java.util.stream.Stream; import software.amazon.awssdk.annotations.NotThreadSafe; @@ -47,6 +48,7 @@ public final class StaticTableMetadata implements TableMetadata { private final Map customMetadata; private final Map indexByNameMap; private final Map keyAttributes; + private final ConcurrentHashMap> partitionKeyCache = new ConcurrentHashMap<>(); private StaticTableMetadata(Builder builder) { this.customMetadata = Collections.unmodifiableMap(builder.customMetadata); @@ -84,6 +86,10 @@ public Optional customMetadataObject(String key, Class objec @Override public List indexPartitionKeys(String indexName) { + return partitionKeyCache.computeIfAbsent(indexName, this::computePartitionKeys); + } + + private List computePartitionKeys(String indexName) { IndexMetadata index = getIndex(indexName); List partitionKeys = index.partitionKeys(); From 3ea4dad6e5b71f6966c93ce4def1e221820a852b Mon Sep 17 00:00:00 2001 From: RanVaknin <50976344+RanVaknin@users.noreply.github.com> Date: Fri, 6 Feb 2026 21:02:05 -0800 Subject: [PATCH 2/3] Add cache for sort key, wrap in unmodifiable list for thread safety --- .../dynamodb/mapper/StaticTableMetadata.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableMetadata.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableMetadata.java index 4bdb79d378e6..9f5d985fc8a8 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableMetadata.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableMetadata.java @@ -49,6 +49,7 @@ public final class StaticTableMetadata implements TableMetadata { private final Map indexByNameMap; private final Map keyAttributes; private final ConcurrentHashMap> partitionKeyCache = new ConcurrentHashMap<>(); + private final ConcurrentHashMap> sortKeyCache = new ConcurrentHashMap<>(); private StaticTableMetadata(Builder builder) { this.customMetadata = Collections.unmodifiableMap(builder.customMetadata); @@ -104,21 +105,25 @@ private List computePartitionKeys(String indexName) { + "Index name: " + indexName); } - return partitionKeys.stream() + return Collections.unmodifiableList(partitionKeys.stream() .filter(Objects::nonNull) .map(KeyAttributeMetadata::name) - .collect(Collectors.toList()); + .collect(Collectors.toList())); } @Override public List indexSortKeys(String indexName) { + return sortKeyCache.computeIfAbsent(indexName, this::computeSortKeys); + } + + private List computeSortKeys(String indexName) { IndexMetadata index = getIndex(indexName); - + List sortKeys = index.sortKeys(); - return sortKeys.stream() - .filter(Objects::nonNull) - .map(KeyAttributeMetadata::name) - .collect(Collectors.toList()); + return Collections.unmodifiableList(sortKeys.stream() + .filter(Objects::nonNull) + .map(KeyAttributeMetadata::name) + .collect(Collectors.toList())); } @Override From 6fc8e69e94bc6ad57b0b8454d7dc87f57ff6c075 Mon Sep 17 00:00:00 2001 From: RanVaknin <50976344+RanVaknin@users.noreply.github.com> Date: Tue, 17 Feb 2026 16:29:05 -0800 Subject: [PATCH 3/3] Add test coverage --- .../mapper/StaticTableMetadataTest.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableMetadataTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableMetadataTest.java index d57784493c69..c4c92609ba6b 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableMetadataTest.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableMetadataTest.java @@ -21,6 +21,7 @@ import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.sameInstance; import static software.amazon.awssdk.enhanced.dynamodb.TableMetadata.primaryIndexName; import java.util.Collection; @@ -359,6 +360,43 @@ public void builderReuse_independentValidation() { assertThat(metadata2.indexPartitionKeys("gsi1"), contains("key1", "key2")); } + @Test + public void indexPartitionKeys_shouldReturnCachedPartitionKeysList() { + StaticTableMetadata metadata = StaticTableMetadata.builder() + .addIndexPartitionKey(primaryIndexName(), + ATTRIBUTE_NAME, + AttributeValueType.S) + .build(); + List first = metadata.indexPartitionKeys(primaryIndexName()); + List second = metadata.indexPartitionKeys(primaryIndexName()); + + assertThat(first, sameInstance(second)); + } + + @Test + public void indexSortKeys_shouldReturnCachedSortKeysList() { + StaticTableMetadata metadata = StaticTableMetadata.builder() + .addIndexSortKey(primaryIndexName(), + ATTRIBUTE_NAME, + AttributeValueType.S) + .build(); + List first = metadata.indexSortKeys(primaryIndexName()); + List second = metadata.indexSortKeys(primaryIndexName()); + + assertThat(first, sameInstance(second)); + } + + @Test + public void indexSortKeys_shouldReturnUnmodifiableList() { + StaticTableMetadata metadata = StaticTableMetadata.builder() + .addIndexSortKey(primaryIndexName(), + ATTRIBUTE_NAME, + AttributeValueType.S) + .build(); + List result = metadata.indexSortKeys(primaryIndexName()); + assertThatThrownBy(() -> result.add("foo")).isInstanceOf(UnsupportedOperationException.class); + } + @Test public void getIndexKeys_partitionAndSort() { TableMetadata tableMetadata = StaticTableMetadata.builder()