4.1 Rebalancing & Group Stability
Module Overview
Goal: Provide the ONE authoritative explanation of rebalancing. This is the ONLY place where rebalancing is explained deeply.
What You'll Learn:
- Why rebalances happen
- Rebalance phases (JoinGroup, SyncGroup)
- Stop-the-world effect
- Eager vs Cooperative rebalancing
- Static membership
- Impact on latency and throughput
- Why rebalances are dangerous in production
- How to minimize rebalances
What's NOT Covered Here:
- Assignment strategies (covered in Module 3.1)
- Poll loop mechanics (covered in Module 6.1)
- Configuration tuning (covered in Module 7.1)
Key Insight: Rebalancing is necessary for elasticity but dangerous for stability. Understanding it is critical for production systems.
1. What is a Rebalance?
Definition
A rebalance is the process of:
- Redistributing partitions among consumers in a group
- Triggered by group membership or metadata changes
- Coordinated by the Group Coordinator
Think of it as reorganizing a team when:
- A new team member joins
- A team member leaves
- Tasks (partitions) are added or removed
Before Rebalance:
Consumer A → Partitions 0, 1
Consumer B → Partitions 2, 3
[Consumer C joins]
After Rebalance:
Consumer A → Partition 0
Consumer B → Partition 2
Consumer C → Partitions 1, 3
2. Why Rebalances Happen
Triggers for Rebalancing
1. Consumer Joins the Group
// New consumer starts
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Collections.singletonList("orders"));
consumer.poll(Duration.ofMillis(100)); // ← Triggers join → Rebalance
When: New consumer instance starts and calls poll().
2. Consumer Leaves the Group
Graceful shutdown:
consumer.close(); // Explicit leave → Rebalance
Ungraceful shutdown:
- Process crash
- Network partition
- Consumer stops sending heartbeats
- Consumer stops calling
poll()withinmax.poll.interval.ms
Result: Coordinator detects dead consumer → Rebalance
3. Consumer Subscription Changes
// Consumer changes what it's subscribed to
consumer.subscribe(Collections.singletonList("orders"));
// Later...
consumer.subscribe(Arrays.asList("orders", "payments")); // → Rebalance
When: Consumer modifies its topic subscription.
4. Topic Partition Count Changes
# Admin adds partitions to topic
kafka-topics.sh --alter --topic orders --partitions 10
Result: New partitions available → Rebalance to assign them.
5. Heartbeat Timeout
Consumer Coordinator
│ │
├──── heartbeat ───────────→ │
│ │
├──── heartbeat ───────────→ │
│ │
│ (heartbeat stops) │
│ │
│ ├─ Timeout!
│ │ session.timeout.ms elapsed
│ └─ Trigger rebalance
When: Consumer misses heartbeats for session.timeout.ms.
6. Poll Timeout
Consumer Coordinator
│ │
├──── heartbeat ───────────→ │ (background thread)
│ │
│ poll() │
│ ... processing ... │
│ ... still processing ... │
│ (no poll for 5+ minutes) │
│ │
│ ├─ Timeout!
│ │ max.poll.interval.ms elapsed
│ └─ Trigger rebalance
When: Consumer doesn't call poll() within max.poll.interval.ms.
Summary of Triggers
| Trigger | Reason | Avoidable? |
|---|---|---|
| Consumer joins | Scaling up | No (intentional) |
| Consumer leaves (graceful) | Shutdown | No (intentional) |
| Consumer leaves (crash) | Process failure | ⚠️ Fix crashes |
| Heartbeat timeout | Network/GC pause | ✅ Tune timeouts |
| Poll timeout | Slow processing | ✅ Tune processing/config |
| Subscription change | Dynamic subscription | ✅ Avoid if possible |
| Partition change | Topic altered | ⚠️ Plan carefully |
3. The Rebalance Protocol (Eager)
Eager Rebalancing Phases
Eager rebalancing (used by Range, RoundRobin, Sticky) has two phases:
Phase 1: JoinGroup
├─ All consumers stop consuming
├─ Consumers send JoinGroupRequest
├─ Coordinator waits for all consumers
├─ Coordinator selects group leader
└─ Leader receives member list
Phase 2: SyncGroup
├─ Leader computes partition assignment
├─ Leader sends assignment to coordinator
├─ Coordinator sends assignment to all consumers
└─ All consumers resume consuming
Key characteristic: All consumers stop consuming during the rebalance (stop-the-world).
Phase 1: JoinGroup
Time Consumer A Consumer B Consumer C Coordinator
────┼─────────────┼─────────────┼─────────────┼──────────────────
1 │ Consuming │ Consuming │ Consuming │
2 │ │ │ (dies) │
3 │ │ │ │ Detects C is dead
4 │ │ │ │ Trigger rebalance
5 │ ← Rebalance │ ← Rebalance │ │ Send to A, B
6 │ Stop ⏸️ │ Stop ⏸️ │ │
7 │ Revoke all │ Revoke all │ │
8 │ JoinGroup →│ │ │
9 │ │ JoinGroup →│ │
10 │ │ │ │ Wait...
11 │ │ │ │ All joined
12 │ ← Leader │ ← Member │ │ Select leader (A)
What happens:
- Coordinator detects change
- Sends rebalance signal to all consumers
- Consumers stop consuming
- Consumers revoke all partitions
- Consumers send JoinGroupRequest
- Coordinator waits for all consumers (or timeout)
- Coordinator selects group leader
Phase 2: SyncGroup
Time Leader (A) Member (B) Coordinator
────┼─────────────┼──────────────┼─────────────────────
13 │ Receives │ │
│ member list │ │
14 │ Computes │ │
│ assignment │ │
15 │ SyncGroup → │ │ (with assignment)
16 │ │ SyncGroup → │ (empty)
17 │ │ │ Distribute assignment
18 │ ← Your │ ← Your │
│ partitions│ partitions │
19 │ Resume ▶️ │ Resume ▶️ │
What happens:
- Leader computes partition assignment
- Leader sends assignment to coordinator
- Coordinator distributes to all consumers
- Consumers receive their partitions
- Consumers resume consuming
Total Rebalance Time
Total rebalance time =
JoinGroup phase
+ SyncGroup phase
+ Time to fetch initial records
Typical: 5-30 seconds
Slow: 30 seconds - 2 minutes
During this time: Zero consumption from the group!
4. Stop-the-World Effect
The Problem
Consumer Group: 10 consumers
Topic: 100 partitions
Rebalance duration: 10 seconds
Impact:
- 10 consumers × 10 seconds = 100 consumer-seconds of downtime
- All 100 partitions paused
- Lag increases during rebalance
- Downstream systems starved
Key Insight: Rebalances are not free. They cause consumption pauses.
Latency Impact
Normal operation:
Consumer → poll() → [records] → process → commit
Latency: ~100ms
During rebalance:
Consumer → poll() → [rebalance!] → wait... → wait... → [records]
Latency: 10+ seconds
Result: p99 latency spikes!
Throughput Impact
Normal: 10,000 msgs/sec consumed
During rebalance: 0 msgs/sec
After rebalance: Catch up at full speed
Metrics:
┌────────────────────────────────┐
│ Throughput │
│ │
│ 10k─┐ ┌────┐ │
│ │ │ │ │
│ 5k─┤ │ │ │
│ │ │ │ │
│ 0──┴────────┴────┴────────────│
│ Normal Rebal Resume │
└────────────────────────────────┘
5. Cooperative Rebalancing
The Evolution
Problem with eager rebalancing:
- All consumers stop, even if they're not affected
- Consumer keeping same partitions still stops
Solution: Cooperative rebalancing (Kafka 2.4+)
- Only consumers losing partitions stop
- Others keep consuming
- May require multiple rounds
Cooperative Protocol
Phase 1: Revoke
├─ Consumers losing partitions revoke them
├─ Other consumers keep consuming ▶️
└─ Partial assignment
Phase 2: Assign
├─ Revoked partitions assigned to new consumers
└─ All consumers now have correct partitions
Key difference: Incremental instead of stop-the-world.
Example: Cooperative vs Eager
Scenario
Initial:
Consumer A: Partitions 0, 1, 2
Consumer B: Partitions 3, 4, 5
Consumer C joins
Goal:
Consumer A: Partitions 0, 1
Consumer B: Partitions 3, 4
Consumer C: Partitions 2, 5
Eager Rebalancing
Time Consumer A Consumer B Consumer C
────┼───────────────┼───────────────┼─────────────
1 │ Consuming 0-2 │ Consuming 3-5 │
2 │ STOP ⏸️ │ STOP ⏸️ │ Joins
3 │ Revoke all │ Revoke all │
4 │ (waiting) │ (waiting) │ (waiting)
5 │ (waiting) │ (waiting) │ (waiting)
6 │ Assigned 0-1 │ Assigned 3-4 │ Assigned 2, 5
7 │ Resume ▶️ │ Resume ▶️ │ Resume ▶️
Downtime: A, B both stopped for full rebalance
Cooperative Rebalancing
Time Consumer A Consumer B Consumer C
────┼───────────────┼───────────────┼─────────────
1 │ Consuming 0-2 │ Consuming 3-5 │
2 │ Revoke 2 │ Revoke 5 │ Joins
3 │ Consuming 0-1▶│ Consuming 3-4▶│ (waiting)
4 │ Consuming 0-1▶│ Consuming 3-4▶│ Assigned 2, 5
5 │ Consuming 0-1▶│ Consuming 3-4▶│ Consuming 2, 5▶
Downtime: None! A and B never fully stopped
Result: Minimal disruption, most consumers keep running.
Cooperative Rebalancing Pros and Cons
Pros:
- ✅ Minimal disruption — most consumers keep consuming
- ✅ Better for high-availability systems
- ✅ Reduced lag spikes
Cons:
- ❌ May require multiple rounds
- ❌ Slightly more complex protocol
- ❌ All consumers must support it (can't mix with eager)
Recommendation: Use CooperativeStickyAssignor for Kafka 2.4+.
6. Static Membership
The Problem with Dynamic Membership
Consumer restarts (e.g., deployment):
1. Consumer A shuts down
2. Rebalance triggered (A removed)
3. Consumer A starts up
4. Rebalance triggered (A joins)
Result: 2 rebalances for 1 restart!
Impact:
- Rolling restarts cause many rebalances
- Each rebalance pauses consumption
- Stateful consumers lose local state
Static Membership Solution
props.put(ConsumerConfig.GROUP_ID_CONFIG, "my-group");
props.put(ConsumerConfig.GROUP_INSTANCE_ID_CONFIG, "consumer-1"); // Static ID
Behavior:
- Consumer has persistent identity
- If consumer rejoins within
session.timeout.ms, no rebalance - Partitions retained across restarts
Static Membership Example
Consumer A (group.instance.id = "consumer-1"):
1. Consumes partitions 0, 1, 2
2. Shuts down (graceful)
3. Coordinator marks as offline (but doesn't remove)
4. Wait session.timeout.ms...
5. If A rejoins within timeout:
→ No rebalance, same partitions
6. If A doesn't rejoin:
→ Rebalance triggered
Key: Rebalance delayed by session.timeout.ms, allowing quick restarts.
Static vs Dynamic Membership
| Aspect | Dynamic | Static |
|---|---|---|
| Member ID | Ephemeral | Persistent (group.instance.id) |
| Restart behavior | Immediate rebalance | Delayed rebalance |
| Failover time | Fast | Slow (waits for timeout) |
| Rolling restarts | Many rebalances | Minimal rebalances |
| Use case | Stateless | Stateful, controlled deployments |
When to Use Static Membership
✅ Use when:
- Consumers maintain local state (caches, RocksDB)
- Rolling restarts are common
- Minimizing rebalances is critical
- You can tolerate delayed failover
❌ Don't use when:
- Consumers are stateless
- Fast failover is critical
- Ad-hoc consumer additions
7. Impact on Production Systems
Why Rebalances Are Dangerous
1. Latency Spikes
p99 latency:
Normal: 50ms
Rebalance: 15 seconds ← SLA violation!
2. Lag Accumulation
During 10-second rebalance:
New messages: 100,000 (at 10k msgs/sec)
Lag increases: +100,000 messages
Time to catch up: Depends on consumer capacity
3. Cascading Failures
Consumer slow → Rebalance triggered
→ Other consumers take over partitions
→ Overload other consumers
→ More slow consumers
→ More rebalances
→ Rebalance storm! 🌪️
4. Duplicate Processing
Consumer A processing message at offset 100
→ Rebalance triggered before commit
→ Consumer B takes over partition
→ Starts from last committed offset (95)
→ Messages 95-100 processed twice!
Rebalance Storms
Rebalance storm: Continuous rebalancing with no stable state.
Causes:
- Consumer processing too slow (
max.poll.interval.mstoo short) - GC pauses (heartbeat timeouts)
- Network issues
- Cascading failures
Symptoms:
- Constant rebalancing in logs
- No stable consumption
- Ever-increasing lag
- Metrics show 0 throughput
Solution:
- Fix root cause (slow processing, GC, network)
- Increase timeouts
- Reduce
max.poll.records - Add more consumers (if underprovisioned)
8. Minimizing Rebalances
Strategy 1: Tune Timeouts
// Increase session timeout (slower failover, fewer false positives)
props.put("session.timeout.ms", 30000); // 30 seconds (default: 10s)
props.put("heartbeat.interval.ms", 10000); // 10 seconds (default: 3s)
// Increase poll interval (allow longer processing)
props.put("max.poll.interval.ms", 600000); // 10 minutes (default: 5m)
Trade-off: Slower failure detection vs fewer false positives.
Strategy 2: Use Cooperative Rebalancing
props.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG,
CooperativeStickyAssignor.class.getName());
Benefit: Partial rebalances instead of stop-the-world.
Strategy 3: Use Static Membership
props.put(ConsumerConfig.GROUP_INSTANCE_ID_CONFIG, "consumer-" + instanceId);
Benefit: Rolling restarts don't trigger rebalances.
Strategy 4: Reduce Slow Processing
// Reduce batch size (faster poll loop)
props.put("max.poll.records", 100); // Default: 500
Trade-off: Lower throughput vs faster polls.
Strategy 5: Optimize Consumer Code
// Bad: Slow processing
while (true) {
records = consumer.poll(Duration.ofMillis(100));
for (record : records) {
slowDatabaseCall(record); // 5 seconds per record!
}
}
// Good: Fast processing
while (true) {
records = consumer.poll(Duration.ofMillis(100));
for (record : records) {
queue.add(record); // Fast, async
}
}
// Separate thread processes queue
Strategy 6: Plan Deployments
For dynamic membership:
- Accept that rolling restarts cause rebalances
- Deploy during low-traffic windows
- Monitor lag during deployments
For static membership:
- Restart quickly (<
session.timeout.ms) - Use health checks to ensure fast startup
- Consider blue-green deployments
9. Detecting Rebalances
In Consumer Logs
[main] INFO o.a.k.c.c.i.AbstractCoordinator - [Consumer clientId=consumer-1, groupId=my-group] Discovered group coordinator broker1:9092
[main] INFO o.a.k.c.c.i.AbstractCoordinator - [Consumer clientId=consumer-1, groupId=my-group] (Re-)joining group
[main] INFO o.a.k.c.c.i.AbstractCoordinator - [Consumer clientId=consumer-1, groupId=my-group] Successfully joined group with generation 5
[main] INFO o.a.k.c.c.i.ConsumerCoordinator - [Consumer clientId=consumer-1, groupId=my-group] Finished rebalance
[main] INFO o.a.k.c.c.i.ConsumerCoordinator - [Consumer clientId=consumer-1, groupId=my-group] Setting offset for partition orders-0 to position 12345
Look for:
- "(Re-)joining group"
- "Successfully joined group with generation X"
- "Finished rebalance"
Using ConsumerRebalanceListener
consumer.subscribe(
Collections.singletonList("orders"),
new ConsumerRebalanceListener() {
@Override
public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
System.out.println("Partitions revoked: " + partitions);
// Commit offsets before losing partitions
consumer.commitSync();
}
@Override
public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
System.out.println("Partitions assigned: " + partitions);
// Initialize state for new partitions
}
}
);
Use cases:
- Commit offsets before revocation
- Save local state
- Initialize state for new partitions
- Metrics/logging
Monitoring Metrics
JMX Metrics:
- kafka.consumer:type=consumer-coordinator-metrics,client-id=*,attribute=rebalance-total
- kafka.consumer:type=consumer-coordinator-metrics,client-id=*,attribute=rebalance-rate-per-hour
- kafka.consumer:type=consumer-coordinator-metrics,client-id=*,attribute=rebalance-latency-avg
Alert on:
- High rebalance rate
- Increasing rebalance frequency
- Long rebalance latency
10. Mental Model Summary
Key Concepts
REBALANCE = Redistribution of partitions
Triggers:
├─ Consumer joins/leaves
├─ Heartbeat timeout
├─ Poll timeout
└─ Partition count change
Types:
├─ Eager: Stop-the-world
└─ Cooperative: Incremental
Impact:
├─ Consumption pause
├─ Latency spike
├─ Lag accumulation
└─ Potential duplicates
The Rebalance Protocol
Eager:
1. All consumers stop
2. Revoke all partitions
3. Compute new assignment
4. All consumers resume
Cooperative:
1. Some consumers revoke partitions
2. Others keep consuming
3. Revoked partitions reassigned
4. Minimal disruption
Questions You Should Be Able to Answer
✅ What is a rebalance?
✅ What triggers a rebalance?
✅ What are the two rebalance phases?
✅ What is the stop-the-world effect?
✅ What's the difference between eager and cooperative rebalancing?
✅ What is static membership and when should you use it?
✅ Why are rebalances dangerous in production?
✅ How can you minimize rebalances?
Questions We Haven't Answered Yet
❌ How are offsets managed during rebalances? → Module 5
❌ What happens in the poll loop? → Module 6.1
❌ How do I tune consumer configs? → Module 7.1
11. What's Next?
Now that you understand rebalancing deeply, the next modules will cover:
- Module 5: Offset management and delivery semantics
- Module 6.1: Poll loop internals
- Module 7.1: Configuration and performance tuning
- Module 8.1: Error handling strategies
Summary
You learned:
- ✅ Rebalancing redistributes partitions among consumers
- ✅ Triggered by membership changes, timeouts, or metadata changes
- ✅ Eager rebalancing stops all consumers (stop-the-world)
- ✅ Cooperative rebalancing minimizes disruption (Kafka 2.4+)
- ✅ Static membership reduces rebalances during restarts
- ✅ Rebalances cause latency spikes, lag, and potential duplicates
- ✅ Minimize rebalances with tuning, cooperative protocols, and static membership
Next up: Offsets Explained (Module 5.1) — Learn what offsets are and how they track consumption progress.