What is Consumer Group
A Consumer Group in Apache Kafka is a logical cooperative cluster of consumer instances (threads or application processes) sharing a common group.id that work together to consum...
🟢 Junior Level
30-Second Summary
A Consumer Group in Apache Kafka is a logical cooperative cluster of consumer instances (threads or application processes) sharing a common group.id that work together to consume and process data from one or more topics in parallel.
Core Rules of a Consumer Group:
- Exclusive Partition Ownership: Each partition in a topic is assigned to at most one consumer instance within the group at any given time, preventing duplicate processing.
- Horizontal Parallelism: If a topic contains 10 partitions and the group has 10 consumers, data is ingested across 10 concurrent streams.
- Group Independence (Pub/Sub): Different consumer groups (e.g.,
group.id = "billing-service"andgroup.id = "audit-analytics") consume the same topic completely independently. Each group maintains its own committed offsets and receives a full, isolated copy of all topic messages.
Real-World Analogy
Think of a Consumer Group as a crew of logistics workers unloading delivery semi-trailers:
- The topic is a fleet of 4 semi-trucks (4 partitions).
- The “Builders” crew (
group.id = "builders") consists of 4 workers (consumers). - The warehouse supervisor assigns each worker to unload exactly one truck. Two builders never climb into the same truck at the same time—they do not get in each other’s way and never unload the same crate twice.
- If an independent “Auditors” crew (
group.id = "auditors") operates at the facility, they inspect the contents of the exact same 4 trucks on their own schedule without interfering with the builders.
Basic Java 21 / Spring Boot 3 Example
@Service
public class OrderNotificationConsumer {
// "notifications-group": multiple replicas of this service share topic partitions
@KafkaListener(
topics = "orders",
groupId = "notifications-group",
concurrency = "3" // Spawns 3 parallel consumer threads inside this JVM instance
)
public void consumeOrder(ConsumerRecord<String, String> record) {
System.out.printf("Partition: %d, Offset: %d, Message: %s%n",
record.partition(), record.offset(), record.value());
}
}
🟡 Middle Level
Consumer-to-Partition Ratios and Idle Consumers
Parallelism in Kafka is bounded strictly by the partition count of the subscribed topics:
| Scenario | Distribution | Behavior & Resource Utilization |
|---|---|---|
| Consumers < Partitions (e.g., 2 consumers, 4 partitions) | C1 $\to$ [P0, P1] C2 $\to$ [P2, P3] |
Both consumers handle multiple partitions. High throughput per worker. |
| Consumers == Partitions (e.g., 4 consumers, 4 partitions) | C1 $\to$ P0, C2 $\to$ P1 C3 $\to$ P2, C4 $\to$ P3 |
Optimal balance. Maximum parallelism without wasted compute resources. |
| Consumers > Partitions (e.g., 6 consumers, 4 partitions) | C1..C4 $\to$ P0..P3 C5, C6 $\to$ IDLE |
Consumers 5 and 6 remain completely idle (standby), consuming no records until an active consumer crashes. |
Offset Storage Architecture: The __consumer_offsets System Topic
In early Kafka versions (pre-0.9), committed consumer offsets were stored in ZooKeeper, which created massive write bottlenecks and cluster instability. Modern Kafka stores all consumer group offsets internally in a dedicated, highly optimized system topic named __consumer_offsets:
- Configured with 50 partitions and a replication factor of 3 by default.
- Record Key:
[group.id, topic, partition](identifies the specific consumer stream). - Record Value:
[offset, leaderEpoch, metadata, commitTimestamp]. - Cleanup Policy:
cleanup.policy = compact(Kafka retains only the latest committed offset record for each group-topic-partition key, discarding historical commits).
What Triggers a Consumer Group Rebalance?
A Rebalance is the distributed protocol that redistributes partition assignments among consumers in a group. It is triggered by the broker in four core events:
- Member Addition: A new consumer joins the group (e.g., autoscaling a microservice up).
- Member Departure: An existing consumer shuts down cleanly or crashes (e.g., pod killed or node hardware failure).
- Partition Alteration: An administrator increases the partition count of a subscribed topic.
- Processing Livelock: An active consumer fails to invoke
poll()withinmax.poll.interval.ms.
🔴 Senior Level
Rebalance Protocols: Eager vs. Cooperative Sticky Rebalance
| Dimension | Legacy Eager Protocol (Kafka < 2.3) | Cooperative Sticky Protocol (Kafka 2.3+ / KIP-429) |
|---|---|---|
| Processing Impact | Stop-the-World: All consumers revoke all partitions and freeze processing (5–30+ seconds). | Incremental: Active consumers keep processing unaffected partitions without pausing. |
| Partition Revocation | 100% of partitions revoked globally across all members. | Only the specific partitions migrating to new members are revoked. |
| Kubernetes Impact | Severe “rebalance storms” during rolling updates, leading to cascading lags and timeouts. | Smooth, transparent rolling updates; zero disruption on unmigrated partitions. |
| Configuration | RangeAssignor / RoundRobinAssignor |
org.apache.kafka.clients.consumer.CooperativeStickyAssignor |
# Recommended configuration for modern mission-critical consumer groups:
partition.assignment.strategy=org.apache.kafka.clients.consumer.CooperativeStickyAssignor
Static Membership (KIP-345): Eliminating Rebalance Storms in Kubernetes
In cloud-native container environments like Kubernetes, pods restart frequently during deployments, node drains, and canary rollouts. Under dynamic membership, every single pod restart triggers two disruptive rebalances:
- Pod terminates $\to$ Member leaves group $\to$ Global rebalance.
- New pod starts $\to$ Member joins group $\to$ Global rebalance.
The Solution — Static Membership: By assigning a persistent, unique identity to each consumer instance:
group.instance.id=payment-service-pod-0
- When a pod restarts, the group coordinator does not trigger an immediate rebalance.
- The coordinator holds the pod’s existing partition assignments for up to
session.timeout.ms(e.g., 45 seconds). - When the restarted pod returns with the same
group.instance.id, it instantly resumes reading its assigned partitions without triggering a group-wide rebalance.
Group Coordinator Selection Mechanics
The broker responsible for coordinating a given consumer group is selected mathematically:
-
The client determines the partition of the offset topic:
\[\text{offsetPartition} = \text{Utils.abs}(\text{groupId.hashCode()}) \pmod{50}\] - The Kafka broker acting as the Leader replica for that specific
__consumer_offsetspartition automatically becomes the Group Coordinator for that group. - All group members direct their
JoinGroup,SyncGroup,Heartbeat, andOffsetCommitrequests to this specific broker.
4 Tricky Questions
1. Why did the legacy Eager Rebalance protocol cause catastrophic “rebalance storms” during Kubernetes rolling updates, and how does Cooperative Sticky Assignor eliminate them?
Answer:
- Under Eager Rebalancing, whenever any pod restarted, the coordinator revoked every single partition from every running instance in the group. All consumer threads halted message processing during the JoinGroup and SyncGroup phases.
- During a rolling deployment of 20 pods, this triggered 40 consecutive Stop-the-World pauses. The accumulated consumer lag overwhelmed pod memory and CPU upon resumption, triggering extended Garbage Collection pauses that breached
max.poll.interval.ms. Consumers were evicted from the group, triggering further rebalances in an endless, self-sustaining failure loop (“Rebalance Storm”). - Cooperative Sticky Rebalance operates incrementally. Unaffected consumers never revoke their partitions and continue processing without interruption. Only partitions migrating between pods are paused, reducing group reassignment latency from minutes to milliseconds.
2. Why can a consumer be expelled from a Consumer Group due to a timeout even though its background Heartbeat Thread is running perfectly and sending heartbeats?
Answer:
- Since Apache Kafka 0.10.1, the Java consumer runs an internal architecture with separated threads:
- The Heartbeat Thread sends background ping requests to the coordinator every
heartbeat.interval.ms(default 3s) to preventsession.timeout.msexpiration. - The Application Thread runs the user’s processing logic and periodically invokes
consumer.poll().
- The Heartbeat Thread sends background ping requests to the coordinator every
- If the application thread hangs on a long-running database lock, slow external HTTP call, or complex computation that exceeds
max.poll.interval.ms(default 5 minutes): - The coordinator assumes the consumer thread is deadlocked or livelocked. Despite valid heartbeats arriving from the background thread, the broker forcefully expels the consumer from the group and initiates a rebalance.
- When the hung consumer thread finally finishes and attempts to commit offsets, Kafka throws a
CommitFailedException.
3. How does Kafka select which physical broker serves as the Group Coordinator, and why does this matter for cluster load balancing?
Answer:
- The coordinator is derived via hashing:
Math.abs(groupId.hashCode()) % offsets.topic.num.partitions(default 50). - The broker hosting the leader replica of that computed
__consumer_offsetspartition becomes the Group Coordinator. - Load Balancing Implication: If an enterprise provisions hundreds of consumer groups that happen to share naming conventions yielding hash collisions onto the same offset partition, a single broker ends up coordinating all heartbeats, joins, and commits for the entire system.
- Designing descriptive, differentiated group naming conventions ensures uniform distribution of coordination workloads across all cluster brokers.
4. What happens if a Kubernetes pod configured with Static Membership (group.instance.id) is killed by OOMKilled and fails to restart within session.timeout.ms?
Answer:
- With Static Membership, the coordinator preserves partition ownership for the disconnected instance until
session.timeout.ms(e.g., 45 seconds) elapses. - During these 45 seconds, the assigned partitions remain completely unread (“frozen”), and incoming records accumulate consumer lag.
- If the pod fails to recover before
session.timeout.msexpires, the coordinator formally registers member death, removes it from group membership, and initiates a rebalance to redistribute its partitions among the surviving live members.
🎯 Interview Cheat Sheet
Core Concepts at a Glance
- Definition: Cooperative pool of consumers sharing partitions under a unified
group.id. - Partition Invariant: 1 topic partition is consumed by at most 1 member per group at any instant.
- Standby Members: Excess consumers exceeding partition count remain idle.
- Pub/Sub Isolation: Different
group.idvalues consume the topic independently with isolated offset trackers. - Offset Storage: Recorded in the internal compacted topic
__consumer_offsets(50 partitions). - Coordinator Broker: Leader of
__consumer_offsetspartition derived viaabs(groupId.hashCode()) % 50. - Cooperative Sticky Assignor: Modern non-blocking assignment protocol replacing Stop-the-World Eager rebalancing.
- Static Membership: Assigning
group.instance.ideliminates rebalances during rolling container restarts.
Rebalance Protocol Comparison
| Metric | Eager Protocol | Cooperative Sticky Protocol |
|---|---|---|
| Pipeline Interruption | Group-wide Stop-the-World pause | Zero pause on unaffected partitions |
| Partition Revocation | Global revocation of all partitions | Incremental revocation of moving partitions |
| Cloud / K8s Suitability | Prone to rebalance storms | Cloud-native standard for containerized workloads |
| Default In | Kafka < 2.3 | Modern Kafka (2.4+) |
Red Flags to Avoid
- ❌ “Two consumers in the same group can read from the same partition concurrently to double processing speed.” (Kafka guarantees single-consumer partition exclusivity).
- ❌ “Kafka consumer offsets are managed and stored in Apache ZooKeeper.” (Deprecated since Kafka 0.9; managed internally via
__consumer_offsets). - ❌ “If the heartbeat thread is alive, Kafka will never evict the consumer.” (Exceeding
max.poll.interval.mswill trigger forced eviction regardless of heartbeats). - ❌ “Enabling static membership means a crashed pod will never trigger a rebalance.” (If
session.timeout.msexpires without recovery, a rebalance is triggered).
Related Topics
- How Does Consumer Balancing Work in a Group — Assignment algorithms (Range, RoundRobin, Sticky)
- Can You Have More Consumers Than Partitions — Idle consumer mechanics
- What Happens When You Add a New Consumer to the Group — Rebalancing lifecycle
- What is Rebalancing and When Does It Happen — Heartbeats, timeouts, and join protocols
- How Does Commit Offset Work — Internal offset commit mechanics