KAFKA-20896: avoid epoch bump when a new config adding to a assignor - #23088
KAFKA-20896: avoid epoch bump when a new config adding to a assignor#23088gabriellefu wants to merge 7 commits into
Conversation
0b68033 to
52e26dd
Compare
| Map<String, String> currentAssignmentConfigs = streamsGroupAssignmentConfigs(groupId); | ||
| Map<String, String> storedAssignmentConfigs = group.lastAssignmentConfigs(); | ||
| if (assignmentUpdate == AssignmentUpdate.NONE && !currentAssignmentConfigs.equals(storedAssignmentConfigs)) { | ||
| if (assignmentUpdate == AssignmentUpdate.NONE |
There was a problem hiding this comment.
The default of a config should be something like 0 or null if it's newly added to a assignor in the future version. In this case, if one config is newly added and not set, it should looks the same after withoutDefaults() when it's the first time added to the config.
mjsax
left a comment
There was a problem hiding this comment.
Claude review:
The comparison logic is sound — I walked group-level vs. broker-level default combinations in both directions and the symmetric strip never suppresses a real change. And it correctly does not change what the assignor sees, which is the right call with pluggable assignors in the picture.
Findings, most important first:
- The javadoc added to streamsGroupAssignmentConfigs is false, and acting on it breaks the assignor. It now claims "A configuration is only included once it is set to a non-default value" — but the body is unchanged and num.standby.replicas is still emitted unconditionally (GroupMetadataManager.java:9917). That's not just a stale comment; it's an invitation to the obvious follow-up, and the follow-up crashes:
// StickyTaskAssignor.java:98-100
localState.numStandbyReplicas =
groupSpec.configs().isEmpty() ? 0
: Integer.parseInt(groupSpec.configs().get("num.standby.replicas"));
A map that is non-empty but lacks the key — e.g. {rack.aware.assignment.tags: "zone"} once standbys are omitted at default — gives parseInt(null) → NumberFormatException. The invariant keeping that unreachable is precisely the unconditional write the JIRA wants removed. Either revert the javadoc to describe what the method does, or make it do what the javadoc says and fix line 98-100 first.
- The footgun is relocated, not removed. The JIRA's complaint is that #22213 fixed one config ad hoc and the next one hits it again. After this PR, adding a config still requires remembering a second, unenforced step — registering it in ASSIGNMENT_CONFIG_DEFAULTS — and forgetting it fails silently with no test failure. Cheap structural guard, worth asking for:
assertEquals(Map.of(), withoutDefaults(streamsGroupAssignmentConfigs(groupId))); // all-default broker + group config
That turns "remember to register the default" into a compile-time-adjacent failure. Supporting evidence that the omission is easy to make: StreamsGroupStaticMemberGroupMetadataManagerTest has to seed getDefaultAssignmentConfigs() into ~12 groups purely to dodge the spurious bump.
-
The new test asserts against a state no broker version can produce. withLastAssignmentConfigs(Map.of("rack.aware.assignment.tags", "")) with the comment "A version that did not omit default-valued configurations recorded the tags at their default". The test does pass, and it does exercise the fix, but only via a side effect: the stored map also lacks num.standby.replicas, so it's the standby-replicas strip doing the work, not the tags strip. Name and narration point at the wrong config. The reachable case is worth testing directly: stored map without a config that current emits at its default.
-
Two mechanisms for one rule. rack.aware.assignment.tags is now handled both by the isEmpty() guard at the source and by the defaults map at comparison time. Keeping the source guard is right (dropping it would start writing tags: "" into every group's record and into the assignor's map), but the relationship should be spelled out, otherwise the next person picks one at random.
Nits: withoutDefaults returns a TreeMap — ordering buys nothing for an equals() comparison; the "num.standby.replicas" / "rack.aware.assignment.tags" literals are now duplicated across two places where a typo silently disables the filter, so shared constants would help; and the new heartbeat(...) helper is dropped between two @test methods rather than with the other helpers.
…nfig when introducing new config in the future
|
Another round from Claude. The first one is subtle, and I am frankly not sure about it -- if the logic is not too complex, and we add a proper comment why we need to treat |
|
updated the pr base on the comment, if one config is not set, it will not be written in __consumer_offset to avoid if a downgraded broker become the broker and don't recognize the config. @mjsax |
change:
add a default map and only trigger a epoch bump for the assignor if it's change from not having the config to default value
added tests
Reviewers: Matthias J. Sax matthias@confluent.io