Skip to content

KAFKA-20169: Support static membership for Kafka Streams with the streams rebalance protocol at Client Side.#22559

Open
chickenchickenlove wants to merge 2 commits into
apache:trunkfrom
chickenchickenlove:KAFKA-20169-CLIENT-CORE
Open

KAFKA-20169: Support static membership for Kafka Streams with the streams rebalance protocol at Client Side.#22559
chickenchickenlove wants to merge 2 commits into
apache:trunkfrom
chickenchickenlove:KAFKA-20169-CLIENT-CORE

Conversation

@chickenchickenlove

@chickenchickenlove chickenchickenlove commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR adds the remaining client-side support for static membership
when Kafka Streams uses the streams rebalance protocol
(group.protocol=streams) introduced by KIP-1071.

The change allows group.instance.id to be used with the streams
protocol, sends the proper static-member leave epoch when a static
Streams member closes with DEFAULT or REMAIN_IN_GROUP, and treats
UNRELEASED_INSTANCE_ID as a known fatal heartbeat error.

Some close-related changes that were part of my previous full PR
#21603 are intentionally not
included here because they are already covered by
#21579, which introduced
CloseOptions.DEFAULT for Kafka Streams.

Changes

  • Allow static membership configuration with group.protocol=streams.
  • Handle UNRELEASED_INSTANCE_ID explicitly in
    StreamsGroupHeartbeatRequestManager.
  • Add coverage for static member close behavior:
    • DEFAULT and REMAIN_IN_GROUP use
      LEAVE_GROUP_STATIC_MEMBER_EPOCH.
    • LEAVE_GROUP uses LEAVE_GROUP_MEMBER_EPOCH.
    • pollOnClose sends the static leave epoch and instance id.
  • Update Streams config tests to verify group.instance.id is allowed
    for unprefixed, consumer., and
    main.consumer. configurations.

Reviewers: Lucas Brutschy lbrutschy@confluent.io

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR completes client-side support for static membership (group.instance.id) when Kafka Streams uses the Streams rebalance protocol (group.protocol=streams, per KIP-1071). It removes prior config validation that blocked static membership under the Streams protocol, adds explicit fatal handling for UNRELEASED_INSTANCE_ID in the Streams heartbeat manager, and extends unit tests to cover the expected close/leave-epoch behavior for static members.

Changes:

  • Allow group.instance.id when group.protocol=streams (including unprefixed, consumer., and main.consumer. configs).
  • Treat UNRELEASED_INSTANCE_ID as a known fatal error in StreamsGroupHeartbeatRequestManager.
  • Add/extend unit tests for static-member close semantics and poll-on-close request contents.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
streams/src/test/java/org/apache/kafka/streams/StreamsConfigTest.java Replaces the previous “should throw” assertion with a parameterized test validating static membership is accepted under Streams protocol and propagated to main consumer configs.
streams/src/main/java/org/apache/kafka/streams/StreamsConfig.java Removes the Streams-protocol compatibility check that rejected group.instance.id, enabling static membership configuration.
clients/src/test/java/org/apache/kafka/clients/consumer/internals/StreamsMembershipManagerTest.java Adds unit tests ensuring static members use the correct leave epoch on close for DEFAULT/REMAIN_IN_GROUP vs LEAVE_GROUP.
clients/src/test/java/org/apache/kafka/clients/consumer/internals/StreamsGroupHeartbeatRequestManagerTest.java Extends fatal-error coverage to include UNRELEASED_INSTANCE_ID and verifies poll-on-close includes static leave epoch + instance id for static members.
clients/src/main/java/org/apache/kafka/clients/consumer/internals/StreamsGroupHeartbeatRequestManager.java Adds explicit fatal handling for UNRELEASED_INSTANCE_ID in error response processing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@lucasbru lucasbru left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR @chickenchickenlove !

Comment on lines +1545 to +1567
@Test
public void testStaticMemberRemainInGroupUsesStaticLeaveEpochOnClose() {
CloseOptions.GroupMembershipOperation operation = CloseOptions.GroupMembershipOperation.REMAIN_IN_GROUP;
MemberState expectedState = MemberState.LEAVING;
int expectedEpoch = StreamsGroupHeartbeatRequest.LEAVE_GROUP_STATIC_MEMBER_EPOCH;
verifyStaticMemberLeaveOnClose(operation, expectedState, expectedEpoch);
}

@Test
public void testStaticMemberDefaultUsesLeaveGroupStaticMemberEpochOnClose() {
CloseOptions.GroupMembershipOperation operation = CloseOptions.GroupMembershipOperation.DEFAULT;
MemberState expectedState = MemberState.LEAVING;
int expectedEpoch = StreamsGroupHeartbeatRequest.LEAVE_GROUP_STATIC_MEMBER_EPOCH;
verifyStaticMemberLeaveOnClose(operation, expectedState, expectedEpoch);
}

@Test
public void testStaticMemberLeaveGroupUsesLeaveGroupEpochOnClose() {
CloseOptions.GroupMembershipOperation operation = CloseOptions.GroupMembershipOperation.LEAVE_GROUP;
MemberState expectedState = MemberState.LEAVING;
int expectedEpoch = StreamsGroupHeartbeatRequest.LEAVE_GROUP_MEMBER_EPOCH;
verifyStaticMemberLeaveOnClose(operation, expectedState, expectedEpoch);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These three only differ by the operation and expected epoch, could they be a single @ParameterizedTest like testPollOnCloseWhenStaticMemberIsLeaving above? Also the names are inconsistent for the same constant: UsesStaticLeaveEpoch vs UsesLeaveGroupStaticMemberEpoch.

final Metrics localMetrics = new Metrics(time);
StreamsMembershipManager membershipManagerWithStaticMember = new StreamsMembershipManager(
GROUP_ID,
Optional.of("instance-1"),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: this hardcodes "instance-1" while StreamsGroupHeartbeatRequestManagerTest uses an INSTANCE_ID constant. Worth a constant here too for consistency.

@@ -1600,12 +1600,6 @@ protected StreamsConfig(final Map<?, ?> props,

private void verifyStreamsProtocolCompatibility(final boolean doLog) {
if (doLog && isStreamsProtocolEnabled()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that group.instance.id is accepted with the streams protocol, are there docs that still state static membership isn't supported here? Worth checking the Streams upgrade/config docs so they don't contradict this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right.
There is indeed an issue there as well. I'll address that part too and include it in this PR.


@ParameterizedTest
@EnumSource(value = CloseOptions.GroupMembershipOperation.class, names = {"DEFAULT", "REMAIN_IN_GROUP"})
public void testPollOnCloseWhenStaticMemberIsLeaving(final CloseOptions.GroupMembershipOperation operation) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage here is unit-level only. Is there an integration/system test exercising static-member close under group.protocol=streams, or is that expected to come separately?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was planning to cover this with a Ducktape test.
I'll take a look at the existing integration tests and check whether this can be covered by adding a new test case.

@chickenchickenlove chickenchickenlove force-pushed the KAFKA-20169-CLIENT-CORE branch from 5d2ace3 to 44d3eb3 Compare June 22, 2026 01:11
@chickenchickenlove

Copy link
Copy Markdown
Contributor Author

@lucasbru
I've rebased onto latest trunk because CI was failed due to 1password error.
Also, I've addressed them based on your comments.
When you get a chance, please take another look. 🙇‍♂️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants