Skip to content

Commit 8b5fc1b

Browse files
MINOR: Make share group state partition metadata unmodifiable (#23888)
Wrap InitMapValue's partitions and ShareGroupStatePartitionMetadataInfo's collections in unmodifiable views, so that the share group state partition metadata cannot be modified through them. Add withAddedPartitions and withRemovedPartitions to InitMapValue to derive new values. Use them in GroupMetadataManager and remove the copies that are no longer needed. combineInitMaps reuses a value when only one of the maps has the topic, and initializeShareGroupState no longer deep copies the initializing topics to remove partitions in place. initializedShareGroupPartitions and sharePartitionsEligibleForOffsetDeletion no longer copy the partitions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Reviewers: David Jacot <dajac@apache.org>
1 parent df536bd commit 8b5fc1b

4 files changed

Lines changed: 69 additions & 40 deletions

File tree

‎group-coordinator/src/main/java/org/apache/kafka/coordinator/group/GroupMetadataManager.java‎

Lines changed: 25 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3335,26 +3335,21 @@ static Map<Uuid, InitMapValue> combineInitMaps(
33353335
Map<Uuid, InitMapValue> initialized,
33363336
Map<Uuid, InitMapValue> initializing
33373337
) {
3338-
Map<Uuid, InitMapValue> finalInitMap = new HashMap<>();
33393338
Set<Uuid> combinedTopicIdSet = new HashSet<>(initialized.keySet());
3339+
combinedTopicIdSet.addAll(initializing.keySet());
33403340

3341-
Set<Uuid> initializingSet = initializing.keySet();
3342-
3343-
combinedTopicIdSet.addAll(initializingSet);
3341+
Map<Uuid, InitMapValue> finalInitMap = new HashMap<>();
33443342

33453343
for (Uuid topicId : combinedTopicIdSet) {
33463344
InitMapValue initializedValue = initialized.get(topicId);
3347-
Set<Integer> finalPartitions = initializedValue != null ? new HashSet<>(initializedValue.partitions()) : new HashSet<>();
3348-
long timestamp = initializedValue != null ? initializedValue.timestamp() : -1;
3349-
String name = initializedValue != null ? initializedValue.name() : "UNKNOWN";
3350-
3351-
if (initializingSet.contains(topicId)) {
3352-
InitMapValue initializingValue = initializing.get(topicId);
3353-
finalPartitions.addAll(initializingValue.partitions());
3354-
timestamp = initializingValue.timestamp();
3355-
name = initializingValue.name();
3345+
InitMapValue initializingValue = initializing.get(topicId);
3346+
if (initializingValue == null) {
3347+
finalInitMap.put(topicId, initializedValue);
3348+
} else if (initializedValue == null) {
3349+
finalInitMap.put(topicId, initializingValue);
3350+
} else {
3351+
finalInitMap.put(topicId, initializingValue.withAddedPartitions(initializedValue.partitions()));
33563352
}
3357-
finalInitMap.putIfAbsent(topicId, new InitMapValue(name, finalPartitions, timestamp));
33583353
}
33593354

33603355
return finalInitMap;
@@ -5913,18 +5908,16 @@ public CoordinatorResult<Void, CoordinatorRecord> initializeShareGroupState(
59135908
// record has up-to-date information.
59145909
Map<Uuid, InitMapValue> finalInitializedMap = combineInitMaps(currentMap.initializedTopics(), enrichedTopicPartitionMap);
59155910

5916-
// Fetch initializing info from state metadata.
5911+
// Fetch initializing info from state metadata, and remove any entries which are already initialized.
59175912
Map<Uuid, InitMapValue> finalInitializingMap = new HashMap<>();
5918-
currentMap.initializingTopics().forEach((k, v) -> finalInitializingMap.put(k, new InitMapValue(v.name(), new HashSet<>(v.partitions()), v.timestamp())));
5919-
5920-
// Remove any entries which are already initialized.
5921-
for (Map.Entry<Uuid, Set<Integer>> entry : topicPartitionMap.entrySet()) {
5913+
for (Map.Entry<Uuid, InitMapValue> entry : currentMap.initializingTopics().entrySet()) {
59225914
Uuid topicId = entry.getKey();
5923-
if (finalInitializingMap.containsKey(topicId)) {
5924-
Set<Integer> partitions = finalInitializingMap.get(topicId).partitions();
5925-
partitions.removeAll(entry.getValue());
5926-
if (partitions.isEmpty()) {
5927-
finalInitializingMap.remove(topicId);
5915+
if (!topicPartitionMap.containsKey(topicId)) {
5916+
finalInitializingMap.put(topicId, entry.getValue());
5917+
} else {
5918+
InitMapValue remaining = entry.getValue().withRemovedPartitions(topicPartitionMap.get(topicId));
5919+
if (!remaining.partitions().isEmpty()) {
5920+
finalInitializingMap.put(topicId, remaining);
59285921
}
59295922
}
59305923
}
@@ -5966,10 +5959,9 @@ public CoordinatorResult<Void, CoordinatorRecord> uninitializeShareGroupState(
59665959
if (!topicPartitionMap.containsKey(topicId)) {
59675960
finalInitializingTopics.put(entry.getKey(), entry.getValue());
59685961
} else {
5969-
Set<Integer> partitions = new HashSet<>(entry.getValue().partitions());
5970-
partitions.removeAll(topicPartitionMap.get(topicId));
5971-
if (!partitions.isEmpty()) {
5972-
finalInitializingTopics.put(entry.getKey(), new InitMapValue(entry.getValue().name(), partitions, entry.getValue().timestamp()));
5962+
InitMapValue remaining = entry.getValue().withRemovedPartitions(topicPartitionMap.get(topicId));
5963+
if (!remaining.partitions().isEmpty()) {
5964+
finalInitializingTopics.put(entry.getKey(), remaining);
59735965
}
59745966
}
59755967
}
@@ -6017,16 +6009,13 @@ private Map<Uuid, InitMapValue> attachInitValue(Map<Uuid, Set<Integer>> initMap)
60176009
public Map<Uuid, Set<Integer>> initializedShareGroupPartitions(
60186010
String groupId
60196011
) {
6020-
Map<Uuid, Set<Integer>> resultMap = new HashMap<>();
6021-
60226012
ShareGroupStatePartitionMetadataInfo currentMap = shareGroupStatePartitionMetadata.get(groupId);
6023-
if (currentMap != null) {
6024-
currentMap.initializedTopics().forEach((topicId, initMapValue) -> {
6025-
resultMap.put(topicId, new HashSet<>(initMapValue.partitions()));
6026-
});
6013+
6014+
if (currentMap == null) {
6015+
return Map.of();
60276016
}
60286017

6029-
return resultMap;
6018+
return stripInitValue(currentMap.initializedTopics());
60306019
}
60316020

60326021
/**
@@ -9485,10 +9474,7 @@ public List<DeleteShareGroupStateRequestData.DeleteStateData> sharePartitionsEli
94859474
return deleteShareGroupStateRequestTopicsData;
94869475
}
94879476

9488-
Map<Uuid, InitMapValue> initializedTopics = new HashMap<>();
9489-
currentMap.initializedTopics().forEach((topicId, initValue) -> {
9490-
initializedTopics.put(topicId, new InitMapValue(initValue.name(), new HashSet<>(initValue.partitions()), initValue.timestamp()));
9491-
});
9477+
Map<Uuid, InitMapValue> initializedTopics = new HashMap<>(currentMap.initializedTopics());
94929478
Set<Uuid> deletingTopics = new HashSet<>(currentMap.deletingTopics());
94939479

94949480
requestData.topics().forEach(topic -> {

‎group-coordinator/src/main/java/org/apache/kafka/coordinator/group/modern/share/ShareGroup.java‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import org.apache.kafka.timeline.SnapshotRegistry;
3333
import org.apache.kafka.timeline.TimelineObject;
3434

35+
import java.util.Collections;
36+
import java.util.HashSet;
3537
import java.util.List;
3638
import java.util.Locale;
3739
import java.util.Map;
@@ -82,6 +84,11 @@ public record ShareGroupStatePartitionMetadataInfo(
8284
Map<Uuid, InitMapValue> initializedTopics,
8385
Set<Uuid> deletingTopics
8486
) {
87+
public ShareGroupStatePartitionMetadataInfo {
88+
initializingTopics = Collections.unmodifiableMap(initializingTopics);
89+
initializedTopics = Collections.unmodifiableMap(initializedTopics);
90+
deletingTopics = Collections.unmodifiableSet(deletingTopics);
91+
}
8592
}
8693

8794
/**
@@ -97,6 +104,29 @@ public record InitMapValue(
97104
Set<Integer> partitions,
98105
long timestamp
99106
) {
107+
public InitMapValue {
108+
partitions = Collections.unmodifiableSet(partitions);
109+
}
110+
111+
/**
112+
* @param added The partitions to add.
113+
* @return A copy of this value with the partitions added.
114+
*/
115+
public InitMapValue withAddedPartitions(Set<Integer> added) {
116+
Set<Integer> newPartitions = new HashSet<>(partitions);
117+
newPartitions.addAll(added);
118+
return new InitMapValue(name, newPartitions, timestamp);
119+
}
120+
121+
/**
122+
* @param removed The partitions to remove.
123+
* @return A copy of this value with the partitions removed.
124+
*/
125+
public InitMapValue withRemovedPartitions(Set<Integer> removed) {
126+
Set<Integer> newPartitions = new HashSet<>(partitions);
127+
newPartitions.removeAll(removed);
128+
return new InitMapValue(name, newPartitions, timestamp);
129+
}
100130
}
101131

102132
/**

‎group-coordinator/src/test/java/org/apache/kafka/coordinator/group/GroupMetadataManagerTest.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30221,7 +30221,7 @@ public void testShareGroupInitializingClearsCommonDeleting() {
3022130221
)
3022230222
);
3022330223

30224-
assertEquals(expectedRecords, records);
30224+
assertRecordsEquals(expectedRecords, records);
3022530225
}
3022630226

3022730227
@Test

‎group-coordinator/src/test/java/org/apache/kafka/coordinator/group/modern/share/ShareGroupTest.java‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.apache.kafka.coordinator.group.Group;
3131
import org.apache.kafka.coordinator.group.modern.Assignment;
3232
import org.apache.kafka.coordinator.group.modern.MemberState;
33+
import org.apache.kafka.coordinator.group.modern.share.ShareGroup.InitMapValue;
3334
import org.apache.kafka.coordinator.group.modern.share.ShareGroup.ShareGroupState;
3435
import org.apache.kafka.timeline.SnapshotRegistry;
3536

@@ -53,6 +54,18 @@
5354

5455
public class ShareGroupTest {
5556

57+
@Test
58+
public void testInitMapValueWithAddedPartitions() {
59+
InitMapValue initMapValue = new InitMapValue("foo", Set.of(0, 1), 1L);
60+
assertEquals(new InitMapValue("foo", Set.of(0, 1, 2), 1L), initMapValue.withAddedPartitions(Set.of(1, 2)));
61+
}
62+
63+
@Test
64+
public void testInitMapValueWithRemovedPartitions() {
65+
InitMapValue initMapValue = new InitMapValue("foo", Set.of(0, 1), 1L);
66+
assertEquals(new InitMapValue("foo", Set.of(0), 1L), initMapValue.withRemovedPartitions(Set.of(1, 2)));
67+
}
68+
5669
@Test
5770
public void testType() {
5871
ShareGroup shareGroup = createShareGroup("foo");

0 commit comments

Comments
 (0)