Rebalancing, liveness and group protocols

Apache Kafka · Advanced · 15 min

Rebalance storms cause lag spikes and duplicates in production; knowing the protocols lets you avoid them.

A rebalance reassigns partitions when group membership or subscriptions change. Consumers prove they are alive in two ways:

  • heartbeats from a background thread; missing them for session.timeout.ms (45 s default) removes the member
  • polling: if the application does not call poll() within max.poll.interval.ms (5 minutes default), the client leaves the group, assuming processing is stuck

Slow processing per batch is the classic cause of rebalance loops: lower max.poll.records (Java) or process asynchronously while pausing partitions.

Assignment strategies:

  • eager (range, round robin): every member gives up all partitions, then gets a new set; all consumption stops briefly
  • cooperative sticky: only partitions that actually move are revoked, in incremental rounds; the others keep flowing

Static membership (group.instance.id set to a stable name per instance) lets a restarted instance reclaim its partitions within the session timeout without any rebalance, which is ideal for rolling deploys on Kubernetes.

Kafka 4.0 made the next-generation consumer group protocol (KIP-848) generally available: the broker computes assignments and members reconcile incrementally, removing the global synchronization barrier. Enable it on clients with group.protocol=consumer. Separately, share groups let many consumers process records from the same partition with per-record acknowledgement, like a queue.

Use rebalance callbacks (on_assign, on_revoke) to commit offsets or flush state before partitions move away.

Example

from confluent_kafka import Consumer

def on_revoke(consumer, partitions):
    consumer.commit(asynchronous=False)        # save progress before handing off
    print("revoked", [p.partition for p in partitions])

def on_assign(consumer, partitions):
    print("assigned", [p.partition for p in partitions])

consumer = Consumer({
    "bootstrap.servers": "localhost:9092",
    "group.id": "billing",
    "group.instance.id": "billing-pod-0",          # static membership
    "partition.assignment.strategy": "cooperative-sticky",
    "session.timeout.ms": 45000,
    "max.poll.interval.ms": 300000,
    "enable.auto.commit": False,
})
consumer.subscribe(["orders"], on_assign=on_assign, on_revoke=on_revoke)
# with group.protocol=consumer (KIP-848), assignment moves to the broker

Remember

  • Heartbeats cover liveness; max.poll.interval.ms covers stuck processing
  • Cooperative sticky moves only the partitions that must move
  • Static membership avoids rebalances on quick restarts
  • KIP-848 (group.protocol=consumer) moves assignment to the broker

Watch out for

  • Doing minutes of work per poll batch and getting kicked out of the group by max.poll.interval.ms.

Check yourself

  1. What does static membership (group.instance.id) prevent?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: kafka.apache.org/43/operations/consumer-rebalance-protocol/

Builds on

Unlocks

Nothing builds on this yet.