Consumer Groups
Consumer groups on KubeMQ — JoinGroup/SyncGroup/Heartbeat coordination, durable per-group offsets, generations, rebalancing, and static membership (KIP-345).
Overview
The Kafka connector implements Kafka's classic consumer-group protocol — the same
FindCoordinator → JoinGroup → SyncGroup → Heartbeat → LeaveGroup sequence every
mainstream Kafka client library (kafka-clients, librdkafka, franz-go, sarama) already
speaks by default. A group of consumers subscribing to the same topic get their partitions
divided among them automatically, their progress recorded durably per group, and their
membership rebalanced whenever the group's shape changes. None of this needs a KubeMQ-specific
concept: group.id is the only setting a client sets, exactly as it would against real Kafka.
Kafka also defines a newer, broker-side KIP-848 next-generation group protocol
(ConsumerGroupHeartbeat/ConsumerGroupDescribe). The connector does not advertise it — only
classic groups run today. A client configured with group.protocol=consumer stalls on connect;
set group.protocol=classic — still every mainstream client's default — to use the protocol
this page describes.
The classic group protocol
Five API calls carry the whole membership lifecycle:
- FindCoordinator locates the broker that owns a given group's state — on a standalone deployment, trivially the node itself.
- JoinGroup is how a consumer enters the group: it names the group, the subscribed topics,
and the assignment strategies it supports. The coordinator collects every member's JoinGroup
and designates one of them the group leader. A first-time dynamic join without a member ID
is briefly turned back with
MEMBER_ID_REQUIRED(error code 79) and asked to retry with the ID the broker just minted — a two-round handshake that stops a network-retry storm from registering duplicate phantom members. Static membership, below, is the one case that skips this round-trip entirely. - SyncGroup is where partitions actually get assigned. The elected leader — not the coordinator — computes the assignment locally, using whichever strategy the group agreed on (range, round-robin, sticky, and so on), and sends that full assignment back through its own SyncGroup call. The coordinator then hands each other member its slice of that same assignment when they call SyncGroup. Assignment is therefore leader-authoritative: the broker distributes an assignment it did not compute.
- Heartbeat keeps a member's membership alive between rebalances; the coordinator times a member out — and removes it — if heartbeats stop arriving within the group's session timeout.
- LeaveGroup is the explicit, immediate departure a client sends on a clean shutdown, so the group doesn't have to wait out a session timeout to notice the member is gone.
Two more calls sit alongside this lifecycle rather than inside it: OffsetCommit and OffsetFetch persist and retrieve a group's position, covered on its own below because it survives across resets of the group protocol itself.
Leader-authoritative assignment and generations
Every completed rebalance bumps the group's generation — a monotonically increasing counter
that fences stale requests. A member that heartbeats or commits using a generation
number the coordinator no longer recognizes is answered with ILLEGAL_GENERATION (error code 22)
rather than silently accepted; this is what stops a member that missed a rebalance from acting on
an assignment that no longer applies. Because assignment itself is leader-computed (above), the
coordinator's own responsibility across a generation is comparatively narrow: collect membership,
forward the leader's assignment to everyone else, and track which generation is current.
Durable per-group offsets
A consumer group's committed position — for every topic-partition it consumes — is stored durably, independent of the group's membership lifecycle. Committing an offset, whether automatically on an interval or explicitly after processing a record, survives every consumer in the group restarting, the group's coordinator failing over to another node, and the group itself going empty and refilling later with a different set of members. See Consuming for the manual-vs-automatic commit mechanics.
Rebalancing
A rebalance reruns JoinGroup/SyncGroup across the whole group, and is triggered by anything that changes what "correct assignment" means:
- A member joins or leaves — explicitly, via
LeaveGroup, or implicitly, via a session-timeout. - A subscribed topic's partition count changes. Growing a topic's partitions is an explicit, operator-triggered action on KubeMQ — partitions only ever increase, never shrink or auto-reshard — and doing so deliberately triggers every subscribed group to rebalance onto the new partition count, rather than leaving some partitions unassigned.
A full rebalance briefly pauses processing for the whole group while JoinGroup/SyncGroup rerun — the cost static membership (below) exists to avoid paying on every routine restart.
Static membership (KIP-345)
A consumer that sets group.instance.id gets a persistent identity the coordinator
remembers across disconnects, instead of a fresh, disposable member ID every time it joins:
- Identity, not just a request field. The coordinator maps that instance ID to a specific member ID once and reuses the mapping on every subsequent join from the same instance — the client doesn't need to cache and resend a member ID itself.
- Static join skips the extra round-trip. A dynamic (non-static) member without a member ID
gets
MEMBER_ID_REQUIREDback, as described above, and has to retry with the ID the broker just handed it. A static member's very firstJoinGroup— carryinggroup.instance.id— resolves straight to its known member ID and is admitted immediately, with no forced retry. - A clean rejoin skips the rebalance entirely. If the group is already stable, its leader hasn't changed, and a static member rejoins with the same subscription it had before — the common case: the process crashed and restarted, or reconnected after a network blip — the coordinator answers at the same generation with no rebalance at all. Every other member in the group is undisturbed. (One exception: if the rejoining instance is the group's current leader, a full rebalance still runs — the fast path is taken only for non-leader members.)
- A displaced or mismatched instance is fenced. If a second connection shows up claiming an
instance ID that's already mapped to a different member ID than the one now presenting it —
the classic double-start-during-a-restart shape — the coordinator refuses it with
FENCED_INSTANCE_ID(error code 82) rather than quietly admitting a second writer under the same identity.
Static membership is supported in full — every one of the four behaviors above holds across
JoinGroup, SyncGroup, Heartbeat, OffsetCommit, and LeaveGroup. It's the mechanism that
makes a rolling restart of a consumer fleet cheap: each pod restarts with the same
group.instance.id it had before, and the group skips a rebalance for the restart itself —
unless the restarting pod is the group's current leader, which still triggers one.
Kafka also has a second, queue-style consumption model — share groups (KIP-932) — where records are individually acquired and acknowledged instead of partition-assigned. It's a genuinely different model from everything on this page, not a variant of classic groups; see Share Groups for the contrast.
Related
Consuming
Subscribe with a consumer group, commit offsets manually or automatically, and seek by offset or timestamp.
Share Groups
Queue-style acquire-and-acknowledge consumption (KIP-932, preview) and how it differs from classic groups.
Topic Mapping
How topics, partitions, and the group and offset channels map onto KubeMQ.
Architecture
The wire-protocol listeners and the dispatch surface these five API calls run through.
Was this page helpful?
Configuration
How the Kafka connector is enabled, ported, and secured — the opt-in CONNECTORS_KAFKA_ENABLE flag, the 9092/9093 listeners, and the settings reference.
Partitions & Ordering
The Kafka partition model on KubeMQ — 1..256 increase-only partitions, client-side key hashing (murmur2 vs CRC32), and the per-partition ordering guarantee.