Skip to content

AxonOps — AI-Native Control Plane for Open Source Data Platforms

Kafka Consumer Group Rebalancing

Consumer group rebalancing redistributes partition assignments among group members. This document covers the rebalance protocol, assignment strategies, and mechanisms to minimize rebalance impact.


TriggerDescription
Consumer joinNew consumer joins group
Consumer leaveConsumer leaves gracefully
Consumer failureHeartbeat timeout
Subscription changeTopic subscription modified
Partition changePartitions added to subscribed topic
Session timeoutConsumer missed heartbeat deadline
Max poll exceededProcessing time exceeded max.poll.interval.ms
Consumer 1Consumer 2Consumer 3Consumer 1Consumer 1Consumer 2Consumer 2Consumer 3Consumer 3P0, P1P2, P3(joining)Rebalance TriggeredRevoke allRevoke allProcessing PAUSEDRebalance CompleteP0P1, P2P3Processing Resumed
ImpactDescription
Processing pauseEager stops all; cooperative/consumer protocol pauses only moved partitions
Increased latencyMessages delayed during rebalance
Duplicate processingAt-least-once semantics may cause reprocessing
State lossIn-memory state may need rebuilding

Consumer 1Consumer 2CoordinatorConsumer 1Consumer 1Consumer 2Consumer 2CoordinatorCoordinatorConsumer 3 joiningRevoke All PartitionsRevoke P0, P1Revoke P2, P3JoinGroupJoinGroupJoinGroupNew consumer also joinsJoinResponse(leader, members)JoinResponseSyncGroupSyncGroup(assignments)Leader computes new assignmentSyncGroup (empty)Assignment [P0]Assignment [P1, P2]Consumer 3 gets P3ResumeAssign P0Assign P1, P2
Consumer 1Consumer 2CoordinatorConsumer 1Consumer 1Consumer 2Consumer 2CoordinatorCoordinatorConsumer 3 joiningFirst Rebalance: Identify ChangesJoinGroup(owned: P0, P1)JoinGroup(owned: P2, P3)Assignment [P0]Only revoke P1Assignment [P2, P3]Keep allRevoke P1 onlyStill processing P0!Second Rebalance: Assign RevokedJoinGroup(owned: P0)JoinGroup(owned: P2, P3)Assignment [P0]Assignment [P2]Consumer 3 gets P1, P3
AspectEagerCooperative
RevocationAll partitions revokedOnly moved partitions revoked
ProcessingFull stop during rebalanceContinues for stable partitions
Rebalance countSingle rebalanceMay require multiple rounds
ComplexitySimpleMore complex
Kafka versionAll versions2.4+

ConsumerCoordinatorConsumerConsumerCoordinatorCoordinatorloop[Normal operation]HeartbeatRequestHeartbeatResponse(error_code=NONE)New consumer joinsHeartbeatRequestHeartbeatResponse(error_code=REBALANCE_IN_PROGRESS)Must rejoin groupJoinGroupRequest
ConfigurationDefaultDescription
session.timeout.ms45000Time to detect consumer failure
heartbeat.interval.ms3000Heartbeat frequency
max.poll.interval.ms300000Max time between poll() calls

Relationship:

session.timeout.ms > heartbeat.interval.ms * 3
Typical: heartbeat = session_timeout / 3
ScenarioTriggerResult
Network partitionNo heartbeatConsumer removed after session.timeout
Long processingpoll() delayedConsumer removed after max.poll.interval
Consumer crashNo heartbeatConsumer removed after session.timeout
Graceful shutdownLeaveGroupImmediate rebalance

Assigns partitions in ranges per topic.

Topic: orders (6 partitions)Topic: events (6 partitions)P0P1P2P3P4P5P0P1P2P3P4P5Consumer 1Consumer 2Consumer 3Range: partitions / consumersC1: 0-1, C2: 2-3, C3: 4-5

Distributes partitions round-robin across consumers.

All Partitions (sorted)events-0events-1events-2orders-0orders-1orders-2Consumer 1Consumer 2Round-robin across all partitionsBetter balance when topics vary

Preserves existing assignments while balancing.

CharacteristicDescription
StickinessMinimizes partition movement
BalanceEnsures even distribution
CooperativeSupports incremental rebalancing
# Configuration
partition.assignment.strategy=org.apache.kafka.clients.consumer.StickyAssignor

Combines sticky assignment with cooperative rebalancing.

# Recommended for Kafka 2.4+
partition.assignment.strategy=org.apache.kafka.clients.consumer.CooperativeStickyAssignor
StrategyBalanceStickinessCooperativeUse Case
RangeGoodSimple, few topics
RoundRobinBestMany topics
StickyGoodStateful processing
CooperativeStickyGoodProduction default

Static membership assigns a persistent identity to consumers, reducing unnecessary rebalances.

ConsumerCoordinatorConsumerConsumerCoordinatorCoordinatorgroup.instance.id = "consumer-1"Initial JoinJoinGroup(group_instance_id="consumer-1")member_id, assignmentConsumer RestartRestartJoinGroup(group_instance_id="consumer-1")Same instance IDSame member_id,same assignmentNo rebalance!
# Consumer configuration
group.instance.id=consumer-instance-1
session.timeout.ms=300000 # 5 minutes (can be longer with static membership)
AspectDynamicStatic
Consumer restartTriggers rebalanceNo rebalance (within timeout)
Session timeoutShort (45s typical)Can be longer (5-30 min)
Member IDAssigned by coordinatorDerived from instance ID
DeploymentRolling restarts cause churnSmooth rolling restarts
BenefitDescription
Reduced rebalancesTransient failures don't trigger rebalance
Faster recoverySame assignment on rejoin
Rolling deploymentsOne consumer at a time without full rebalance
Stable stateKafka Streams state stores remain local

# Increase session timeout for static membership
session.timeout.ms=300000
# Use cooperative rebalancing
partition.assignment.strategy=org.apache.kafka.clients.consumer.CooperativeStickyAssignor
# Static membership
group.instance.id=my-consumer-instance-1
# Faster heartbeat
heartbeat.interval.ms=1000
# Shorter JoinGroup timeout
max.poll.interval.ms=30000 # Reduce if processing is fast
# Pre-warm consumer before starting poll
# (Initialize resources before subscribing)
// Pattern: Pause partitions during long processing
consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
// Pause to prevent rebalance
consumer.pause(consumer.assignment());
// Long processing
processRecord(record);
// Resume
consumer.resume(consumer.assignment());
}

consumer.subscribe(Arrays.asList("orders"), new ConsumerRebalanceListener() {
@Override
public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
// Called BEFORE rebalance
// Commit offsets for revoked partitions
Map<TopicPartition, OffsetAndMetadata> offsets = new HashMap<>();
for (TopicPartition partition : partitions) {
offsets.put(partition, new OffsetAndMetadata(currentOffset(partition)));
}
consumer.commitSync(offsets);
// Flush any buffered data
flushState(partitions);
}
@Override
public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
// Called AFTER rebalance
// Initialize state for new partitions
for (TopicPartition partition : partitions) {
initializeState(partition);
}
}
@Override
public void onPartitionsLost(Collection<TopicPartition> partitions) {
// Called when partitions lost without revoke (cooperative)
// State may already be invalid
log.warn("Partitions lost: {}", partitions);
}
});
// With cooperative rebalancing
@Override
public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
// Only revoked partitions, not all assigned
// Other partitions continue processing
commitOffsetsForPartitions(partitions);
}
@Override
public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
// Only newly assigned partitions
// Existing partitions unchanged
initializeStateForPartitions(partitions);
}

MetricDescriptionAlert
rebalance-latency-avgAverage rebalance duration> 30s
rebalance-latency-maxMaximum rebalance duration> 60s
rebalance-totalTotal rebalance countIncreasing rapidly
last-rebalance-seconds-agoTime since last rebalance-
failed-rebalance-totalFailed rebalances> 0
Terminal window
# Check consumer group state
kafka-consumer-groups.sh --bootstrap-server kafka:9092 \
--describe --group my-group
# Monitor group membership changes
kafka-consumer-groups.sh --bootstrap-server kafka:9092 \
--describe --group my-group --members
# Check for rebalance in logs
grep -i "rebalance\|revoke\|assign" /var/log/kafka/consumer.log
IssueSymptomSolution
Frequent rebalancesHigh rebalance-totalEnable static membership
Long rebalancesHigh rebalance-latencyReduce group size, use cooperative
Consumer timeoutsMembers leavingIncrease max.poll.interval.ms
Uneven assignmentImbalanced lagCheck assignment strategy

Before: 2 consumers, 6 partitionsAfter: 3 consumersC1: P0,P1,P2C2: P3,P4,P5C1: P0,P1C2: P2,P3C3: P4,P5Automatic rebalancedistributes partitionsAdd consumer
ConstraintDescription
Max consumers = partitionsExtra consumers idle
Adding partitionsChanges key distribution
Consumer group sizeLarger groups = longer rebalances
PracticeRationale
Plan partition count for growthAvoid adding partitions later
Use static membershipSmooth scaling operations
Scale graduallyOne consumer at a time
Monitor during scaleDetect issues early

EmptyPreparingRebalanceCompletingRebalanceStableDeadMember joinsAll members joinedAll members syncedMember changeAll members leaveGroup expires
StateDescription
EmptyNo active members
PreparingRebalanceWaiting for members to join
CompletingRebalanceWaiting for SyncGroup
StableNormal operation
DeadGroup being deleted

KIP-848 introduces a fundamentally redesigned consumer rebalance protocol that moves partition assignment from the client to the server (broker). This eliminates the multi-round rebalance problem of cooperative rebalancing and provides truly seamless partition reassignment.

AspectClassic ProtocolKIP-848 Protocol
Assignment locationConsumer (leader)Broker (group coordinator)
Rebalance rounds1 (eager) or 2+ (cooperative)Single notification
Stop-the-worldYes (eager) or partial (cooperative)No global stop-the-world
Protocol complexityClient-side logicServer-side logic
Kafka versionAll versions4.0+ (GA)
Kafka VersionKIP-848 StatusNotes
< 3.7❌ Not availableUse classic protocol
3.7.x⚠️ Early accessUse classic protocol for production
3.8.x⚠️ Early accessImproved stability
4.0+✅ Stable (GA)Production ready, recommended for new deployments

Version guidance

Kafka docs state KIP-848 is GA in 4.0. This guide treats 3.7/3.8 as early access.

Consumer 1Consumer 2Group CoordinatorConsumer 1Consumer 1Consumer 2Consumer 2Group CoordinatorGroup CoordinatorOwns P0, P1Owns P2, P3Consumer 3 JoinsCoordinator computesnew assignmentAssignment [P0]Revoke P1Assignment [P2]Revoke P3Consumer 3 gets P1, P3Release P1,continue P0Release P3,continue P2No stop-the-world!Processing continues

Key difference: The broker computes and pushes assignments directly to consumers. Consumers do not need to coordinate with each other through JoinGroup/SyncGroup rounds.

# Enable KIP-848 protocol (Kafka 3.7+)
group.protocol=consumer
# Classic protocol (default, backward compatible)
group.protocol=classic

Broker configuration (Kafka 3.7-3.8 early access):

# Enable consumer protocol (4.0+)
group.version=1
# Enabled protocols (default includes classic, consumer, streams)
group.coordinator.rebalance.protocols=classic,consumer,streams

Streams Rebalance Protocol (KIP-1071)

The streams protocol enables server-side rebalance for Kafka Streams applications, with broker-coordinated task assignment. Introduced as early access in Kafka 4.1, it is generally available in Kafka 4.2.

Classic Protocol (Cooperative)Round 1Round 2KIP-848 ProtocolSingle OperationJoinGroupSyncGroupIdentify partitions to revokeAssign revoked partitionsConsumerGroupHeartbeatAssignmentBroker pushes new assignmentPartitions revoked
AspectCooperative (Classic)KIP-848
Rebalance triggerHeartbeat returns REBALANCE_IN_PROGRESSHeartbeat returns new assignment
Assignment computationLeader consumerGroup coordinator
Partition releaseConsumer-initiated after SyncGroupConsumer-initiated after heartbeat
New partition assignmentRequires second rebalance roundImmediate in same response
Minimum latency2× heartbeat interval1× heartbeat interval

From Classic to KIP-848:

  1. Upgrade brokers to Kafka 3.7+ (early access) or 4.0+ (stable)
  2. Enable new coordinator on brokers (3.7-3.8 only)
  3. Upgrade consumers to compatible client version
  4. Set group.protocol=consumer on consumers
  5. Rolling restart consumers (group will have mixed protocols temporarily)

Mixed Protocol Groups

During migration, a consumer group can have members using both protocols. The coordinator handles this gracefully, but full benefits are only realized when all members use KIP-848.

BenefitDescription
Lower latencySingle round-trip vs multiple rounds
No stop-the-worldStable partitions continue processing
Simpler clientsAssignment logic moved to broker
Better scalabilityReduced coordination overhead
Predictable behaviorBroker has global view of group
LimitationDescription
Kafka versionRequires 3.7+ (early access) or 4.0+ (stable)
Client supportRequires updated client libraries
Custom assignorsServer-side assignors only
Feature maturityEarly access in 3.7/3.8

New metrics for KIP-848 protocol:

MetricDescription
group-coordinator-metricsNew coordinator metrics
consumer-group-heartbeat-rateHeartbeat frequency
consumer-group-rebalance-rateAssignment change frequency

FeatureKafka Version
Basic rebalancing0.9.0+
Sticky assignor0.11.0+
Static membership2.3.0+
Cooperative rebalancing2.4.0+
KIP-848 (GA)4.0.0+
Streams Rebalance Protocol (GA)4.2.0+ (KIP-1071)
Assignment epochs4.3.0+ (KIP-1251)

Assignment Epochs (Kafka 4.3+)

KIP-1251 introduces monotonically increasing assignment epochs for consumer and share groups. Each new assignment carries a strictly higher epoch than the previous one. Consumers and share-group members include the observed epoch in subsequent heartbeats so the coordinator can detect stale assignment state and reconcile it before the next rebalance, reducing the window in which a member acts on an obsolete view of the group.

Deprecated: `group.coordinator.rebalance.protocols`

In Kafka 4.3 the broker-level group.coordinator.rebalance.protocols configuration is deprecated (KAFKA-19740). Operators upgrading to 4.3 should plan for removal in a future release; protocol enablement will be governed by other mechanisms.