Cooperative Sticky Rebalancing in Kafka

Cooperative Sticky Rebalancing in Kafka

Every time a consumer joins, leaves or crashes, Kafka must share the partitions again. Done the old way, everybody stops working while it happens. This guide explains the newer way, where only the partitions that truly need to move are touched, and everyone else keeps going.

It is Friday afternoon, and a team is deploying a new version of an order-processing service. The service runs as forty identical instances, and all of them read from the same Kafka topic. The deployment tool restarts the instances one after another. After each restart, the dashboard shows the same picture: processing stops for several seconds, the backlog grows, and then everything catches up. By the fortieth restart, the team has watched forty small outages, and the customers have noticed slow order confirmations.

Nothing is broken. This is simply what happens when a group of Kafka consumers has to reshuffle its work and does it the “stop everything” way. The reshuffling is called a rebalance. Since Kafka 2.4, there is a gentler alternative called cooperative rebalancing, and it is usually paired with a partition assignment method called sticky. Together they form the CooperativeStickyAssignor.

This guide explains it from the ground up. You will learn what a rebalance is and why it hurts, what “sticky” and “cooperative” each mean, how the two-round dance works step by step, what your own code must do differently, how to upgrade a live system safely, and how to monitor it. Every new word is explained when it first appears, and each big idea comes with an everyday comparison and worked numbers.

KKafka in Five Minutes

A short refresher, so that the rest of the guide stands on firm ground.

Apache Kafka moves streams of events between programs. A program that writes events is a producer. A program that reads them is a consumer. The servers in the middle are brokers. Events are organised into named streams called topics, and every topic is split into partitions. A partition is an ordered list of messages, and each message in it has a position number called its offset.

Partitions exist so that work can be shared. One partition is read by one consumer at a time, so a topic with twelve partitions can keep up to twelve consumers busy at once. Each consumer remembers how far it has read by saving an offset, which is called committing the offset. If the consumer restarts, it resumes from the committed position.

Everyday analogy

Think of a restaurant. The tables are the partitions, the waiters are the consumers, and the orders taken at each table are the messages. A table is served by one waiter at a time, so that orders do not get mixed up. The whole guide is about how the restaurant reassigns tables when waiters arrive, leave, or go on break.

Stream

Topic

A named channel of events, such as orders or payments.

Unit of work

Partition

One ordered slice of a topic. It is read by only one consumer of a group at a time.

Reader

Consumer

A program that reads messages from the partitions assigned to it.

Bookmark

Committed offset

The saved position that lets a consumer resume after a restart or a handover.

i
The rule that makes rebalancing necessary

Within a consumer group, each partition must have exactly one owner at any moment. Whenever the list of consumers or the list of partitions changes, ownership must be worked out again. That is all a rebalance is.

CConsumer Groups: Sharing the Work

A consumer group is a team of consumers that read one or more topics together, splitting the partitions between them.

All consumers that use the same group.id belong to the same consumer group. Kafka divides the topic’s partitions among the members so that every partition is read by exactly one of them. If there are more partitions than members, some members read several partitions. If there are more members than partitions, the extra members sit idle, waiting for a chance to help.

flowchart LR
  T["Topic orders
6 partitions"] --> P0["Partition 0"] T --> P1["Partition 1"] T --> P2["Partition 2"] T --> P3["Partition 3"] T --> P4["Partition 4"] T --> P5["Partition 5"] P0 --> C1["Consumer 1"] P1 --> C1 P2 --> C2["Consumer 2"] P3 --> C2 P4 --> C3["Consumer 3"] P5 --> C3
Figure 1: six partitions shared by three consumers in one group, two partitions each.

The group coordinator

One broker acts as the group coordinator for each group. It keeps the list of members, receives their heartbeats, stores their committed offsets, and decides when a rebalance must start. Consumers find it by asking any broker, which works it out from the cluster’s metadata.

Two groups never interfere

Different groups read the same topic independently. A payment topic might be read by a billing group and, separately, by a fraud-detection group. Each group has its own members, its own assignment and its own committed offsets. A rebalance in one group has no effect on the other.

1 owner
per partition, per group, at any moment
Many
groups can read the same topic independently
1 broker
coordinates each group

WWhat Is a Rebalance, and Why Does It Happen?

A rebalance is the process of recalculating who owns which partition. It is not a fault. It is a normal part of life, but it has a price.

The coordinator starts a rebalance whenever something changes in a way that makes the current assignment wrong or unsafe. Understanding the triggers helps you predict how often rebalances will happen in your own system.

TriggerExampleHow common
A consumer joinsA new instance starts, or autoscaling adds capacityEvery deployment and every scale-up
A consumer leaves cleanlyAn instance shuts down and says goodbyeEvery deployment and scale-down
A consumer is declared deadIt crashes, or stops sending heartbeatsOccasional, but disruptive
A consumer is too slowIt does not call poll again within the allowed timeUsually a symptom of slow processing
Partitions changeAn operator adds partitions to a subscribed topicRare
Subscriptions changeA pattern subscription matches a newly created topicDepends on how topics are created
The coordinator movesThe broker acting as coordinator fails overRare

The life of a group

The coordinator tracks each group through a small set of states. A group is Empty when it has no members. When a member joins, it enters PreparingRebalance while the coordinator waits for members to report in. Then it moves to CompletingRebalance while the assignment is worked out and handed out. Once everyone has their share, it becomes Stable. Any new trigger sends it back to PreparingRebalance.

stateDiagram-v2
  [*] --> Empty
  Empty --> PreparingRebalance : first member joins
  PreparingRebalance --> CompletingRebalance : all members have joined
  CompletingRebalance --> Stable : assignment received
  Stable --> PreparingRebalance : membership or subscription change
  Stable --> Empty : last member leaves
  Empty --> [*] : group removed
Figure 2: the states of a consumer group as seen by its coordinator.
i
See the state yourself

The standard consumer-groups command-line tool can describe a group and show its state. If you see PreparingRebalance or CompletingRebalance for a long time, or over and over, you have found a rebalance problem.

PThe Classic Rebalance Protocol, Step by Step

Before comparing old and new styles, we need to see the basic conversation that every rebalance uses.

The classic protocol is built from a few requests. A JoinGroup request says “I want to be in the group, and here is what I subscribe to”. A SyncGroup request says “I am ready to receive my assignment”. A Heartbeat request says “I am still alive”. A LeaveGroup request says “I am leaving”. Interestingly, the coordinator does not compute the assignment itself. It picks one member to be the group leader, and that member’s code does the calculation.

sequenceDiagram
  participant M as Member
  participant L as Group leader
  participant C as Coordinator
  M->>C: JoinGroup with my subscription
  L->>C: JoinGroup with my subscription
  Note over C: Waits for all members or the rebalance timeout
  C-->>L: Here are all members and their subscriptions
  C-->>M: You are in, generation 5
  Note over L: Runs the assignor and builds the plan
  L->>C: SyncGroup with the full plan
  M->>C: SyncGroup with no plan
  C-->>L: Your assignment
  C-->>M: Your assignment
Figure 3: the leader computes the plan, and the coordinator hands each member its part.
1

Members join

Each member sends JoinGroup with the topics it wants and the assignment methods it supports. The coordinator waits until everyone has reported in, or until a time limit passes.

2

The coordinator chooses a leader and a method

It picks one member as leader and chooses an assignment method that all members support. The leader receives the full list of members and their subscriptions.

3

The leader computes the plan

Using the chosen assignor, the leader decides which member gets which partitions, based on the partition lists that come from cluster metadata.

4

Everyone syncs

The leader sends the plan in SyncGroup. The coordinator gives each member only its own part of the plan in the reply.

5

Members start work

Each member now knows its partitions, fetches the committed offsets, and begins reading. The group is Stable.

Everyday analogy

At the start of a shift, all waiters gather. The manager picks one senior waiter to draw up the table plan. Everyone tells the senior waiter which sections they can cover, the plan is written down, and each waiter is handed their own list of tables. Only then does service resume.

!
The barrier

Steps 1 to 4 form a barrier: no member can get its final assignment until all members have reported. In the older, “eager” style of rebalancing, this barrier also means everybody has stopped working while they wait. That is the heart of the problem the rest of this guide solves.

DA Day in the Life of a Group Member

Following one consumer from start to shutdown shows where every rebalance concept fits.

Consider a single consumer instance, from the moment its process starts until it is stopped. Every stage below touches something we have discussed, and knowing the order helps you place problems when they appear.

1

Start and subscribe

The application creates a consumer, gives it the group name, the topics it wants, and its list of assignors. Nothing has happened on the network yet.

2

Find the coordinator

On the first poll, the consumer asks any broker which broker coordinates its group, and connects to it.

3

Join and sync

It sends JoinGroup with its subscription, and, if cooperative, with the partitions it owns. Here that list is empty. It receives its generation and, after SyncGroup, its assignment.

4

Load starting positions

For each assigned partition, it asks the coordinator for the committed offset and starts reading from there, or from a configured default if no commit exists.

5

Work: poll, process, commit

The steady state. The application calls poll, processes records, and commits offsets. A background thread sends heartbeats.

6

Rebalances come and go

Sometimes a heartbeat reply says a rebalance is in progress. The consumer rejoins, possibly loses or gains a few partitions, and goes on working.

7

Shutdown

On a clean stop, the application closes the consumer, which commits, leaves the group with a LeaveGroup request, and releases its partitions so others can take them at once.

StageWho is involvedWhat can go wrong
Find coordinatorAny broker, then the coordinatorWrong addresses, or a coordinator that just moved
Join and syncCoordinator and group leaderSlow members holding up the join phase
Load positionsCoordinatorNo committed offset, so the default reset policy applies
Steady workPartition leadersSlow processing exceeding the poll interval
RebalanceEveryone in the groupListener bugs, long callbacks
ShutdownCoordinatorKilled without closing, so others wait for the session timeout
Everyday analogy

A waiter arrives, signs in with the manager, receives a list of tables, checks the notes left by the previous waiter, serves guests through the evening, adjusts when the manager reassigns a table, and finally signs out at closing time so that nobody waits for them. A waiter who leaves without signing out is only missed when the manager finally notices.

i
Close the consumer

Stopping a consumer without closing it forces the group to wait for the session timeout before reassigning its partitions. A clean close makes the handover immediate, and is one of the cheapest improvements available.

AAssignors: Who Decides Which Consumer Gets What

An assignor is the piece of code that turns “these members and these partitions” into “this member gets these partitions”. Kafka ships with several.

The setting partition.assignment.strategy lists the assignors a consumer supports. The coordinator picks one that every member of the group supports. The four built-in choices differ in how they divide the work and in how they behave when the group changes.

AssignorHow it divides partitionsKeeps existing owners?Rebalance style
RangeAssignorSorts partitions of each topic and gives each consumer a contiguous rangeNot by designEager
RoundRobinAssignorDeals partitions out one by one, like cardsNot by designEager
StickyAssignorBalances evenly, then tries to keep as many existing owners as possibleYesEager
CooperativeStickyAssignorSame idea as sticky, but moves partitions in stagesYesCooperative

What the defaults are

Since Kafka 3.0, the default list is the range assignor first and the cooperative sticky assignor second. With a fresh group this means Range is used, because it is listed first and everyone supports it. The second entry is there to make a smooth upgrade possible: removing the first entry from all members in a rolling restart moves the group to cooperative behaviour without any special steps. Older Kafka versions used only the range assignor by default.

Range

Good for co-partitioned topics

Partition 0 of every topic goes to the same consumer, which helps when joining streams keyed the same way.

Round robin

Very even spread

Deals partitions of all topics like cards, giving an even count, but it moves many partitions when the group changes.

Sticky

Even and stable

Keeps the load even and disturbs as few partitions as it can, but still asks every member to stop during a rebalance.

Cooperative sticky

Even, stable and non-stop

Keeps the load even, moves few partitions, and lets everyone else keep working while it does so.

i
Two separate ideas

“Sticky” describes which assignment is chosen: one that changes as little as possible. “Cooperative” describes how the change is carried out: in stages, without a full stop. The two ideas are independent, and the cooperative sticky assignor combines them.

EEager Rebalancing: Stop the World

To appreciate the cooperative approach, we first need to feel the pain of the eager one.

In eager rebalancing, whenever a rebalance starts, every member gives up every partition it owns. It stops fetching, runs its cleanup code, commits its offsets, and sends JoinGroup with nothing in hand. Only after the new plan arrives does it receive partitions again, possibly the same ones it had a moment ago. Between the first revocation and the arrival of the plan, the entire group processes nothing.

flowchart TD
  A["Rebalance triggered"] --> B["Every member revokes all its partitions"]
  B --> C["Every member sends JoinGroup"]
  C --> D["Coordinator waits for all members"]
  D --> E["Leader computes the new plan"]
  E --> F["Every member receives its assignment"]
  F --> G["Every member resumes reading"]
Figure 4: in eager rebalancing, the whole group is idle from the first revocation until the last assignment.
Everyday analogy

A new waiter joins mid-service. The manager rings a bell and shouts, “Everyone stop! Hand back all your tables!” Every waiter finishes what they hold, walks to the front, and waits. The manager draws a new plan, reads it out, and only then does anyone go back to the floor. Most waiters end up with the same tables they had before, but every table went unserved in the meantime.

What it costs

Pause

The whole group stops

Even consumers that will end up with exactly the same partitions stop reading. Lag builds up on every partition, not only the ones that move.

State

Local state is thrown away

Caches, in-memory aggregations and open resources tied to a partition are usually cleared on revoke, and must be rebuilt after the rebalance.

Duplicates

More reprocessing

Work done since the last commit may be repeated after a handover, so a full stop risks more duplicates across the whole group.

Cascades

Storms during deployments

A rolling restart of many instances triggers one full stop after another, so the pauses add up instead of overlapping.

40
instances restarted one at a time
2 x 40
rebalances if each restart both leaves and rejoins
Every
partition paused during every one of them

The last point deserves attention. With forty instances restarting in sequence, a group can go through dozens of rebalances during one deployment. If each takes ten seconds of complete silence, that is several minutes of lost processing spread across the release, even though the deployment itself was “rolling” and supposedly gentle.

SWhat “Sticky” Means

Stickiness answers a simple question: when the group changes, why should a consumer lose partitions it could have kept?

A sticky assignor has two goals, in this order. First, keep the assignment balanced, meaning the number of partitions per consumer differs by at most one. Second, subject to that, keep the assignment as similar as possible to the previous one. In other words, it moves the fewest partitions that are needed to reach a fair split.

A worked example

Take six partitions and three consumers. Each consumer owns two partitions. Now a fourth consumer joins. With four consumers, a balanced split gives two consumers two partitions each and two consumers one partition each, since six divided by four is one and a half. The question is how many partitions must move to reach that.

ApproachConsumer 1Consumer 2Consumer 3Consumer 4Partitions moved
Before the joinP0, P1P2, P3P4, P5none–
Non-sticky (range) after the joinP0, P1P2, P3P4P51 in this lucky case, but often more
Round robin after the joinP0, P4P1, P5P2P35
Sticky after the joinP0, P1P2, P3P4P51

The sticky answer changes exactly one thing: partition P5 moves from Consumer 3 to the new Consumer 4. Everyone else keeps precisely what they had. Other methods can reshuffle many partitions for no good reason, because they build each assignment from scratch without remembering the last one.

Everyday analogy

Waiters build up knowledge of their tables: which guests want extra bread, which table is near the draught. When a new waiter joins, a sensible manager takes one table from someone and gives it to the newcomer, rather than redrawing the whole plan and making everyone learn new tables from scratch.

Why stickiness matters

What stickiness protects

  • Caches and lookup tables built for a partition
  • Open connections and prepared resources
  • State that a consumer has warmed up over time
  • Fewer handovers, so fewer possible duplicates

What stickiness does not fix

  • The pause, if the protocol is still eager
  • Uneven load between busy and quiet partitions
  • Co-partitioning that the range method gave you
  • Slow consumers that hold up a rebalance
!
Sticky alone is not enough

The plain StickyAssignor picks a gentle assignment but still uses the eager protocol. Everyone still stops and revokes everything, and only then gets most of it back. The benefit of stickiness is fully realised only when combined with the cooperative protocol, which is the next idea.

OWhat “Cooperative” Means

Cooperative rebalancing changes the protocol so that members give up only what they must, and keep working on everything else.

The idea, introduced in Kafka 2.4 by a proposal known as KIP-429, is called incremental cooperative rebalancing. Instead of revoking everything at the start, each member keeps its partitions when the rebalance begins. It reports to the group leader exactly which partitions it currently owns. The leader then plans the new assignment and works out which partitions must move from one owner to another.

Here comes the clever rule: a partition is never handed directly from one owner to another in the same round. If a partition must move, the leader first takes it away from the old owner and leaves it unowned for that round. The old owner sees that it lost the partition, cleans up and commits, and then asks to rejoin. In a second round, the free partition is given to its new owner. Two rounds, but only the moved partitions are ever idle.

Everyday analogy

A new waiter joins, and the manager quietly says to one waiter, “Please finish up table five and hand it back.” Every other waiter keeps serving. Once table five is handed back, the manager gives it to the newcomer. Nobody rang a bell and nobody stopped.

QuestionEagerCooperative
What is revoked at the start?Every partition of every memberNothing
What does JoinGroup carry?The subscription onlyThe subscription plus the partitions the member owns
Who stops working?The whole groupOnly owners of partitions that must move
How many rounds?OneAt least two when something must move
Is a partition ever unowned?Briefly, for all partitionsBriefly, only for moved partitions
Are duplicates more or less likely?More, since everything is handed overFewer, since only some things are

Why the safety rule matters

Why not hand the partition straight to the new owner in one round? Because two consumers might then read and process the same partition at the same instant: the old one, which has not yet noticed, and the new one. That breaks the rule that each partition has exactly one owner. By forcing a gap, cooperative rebalancing guarantees that the old owner has let go before the new owner starts. The price is one extra round, and that price is paid only by the partitions that move.

i
Cooperative sticky in one sentence

Choose the assignment that moves the fewest partitions, and carry out the move by taking each moved partition away first and giving it to its new owner in the next round, while everything else keeps running.

XA Worked Example: Scaling Out Without Stopping

Let us follow the six-partition example again, this time with the cooperative protocol, and count exactly who stops and for how long.

The group starts with three consumers, each owning two partitions: Consumer 1 has P0 and P1, Consumer 2 has P2 and P3, and Consumer 3 has P4 and P5. A fourth consumer starts up and joins. The coordinator begins a rebalance, and the group enters its first round.

sequenceDiagram
  participant C3 as Consumer 3
  participant C4 as Consumer 4
  participant L as Leader and coordinator
  Note over C3,C4: Round 1
  C3->>L: JoinGroup, I own P4 and P5
  C4->>L: JoinGroup, I own nothing
  L-->>C3: Assignment is P4 only, give up P5
  L-->>C4: Assignment is empty for now
  Note over C3: Revoke P5, commit its offset, rejoin
  Note over C3,C4: Round 2
  C3->>L: JoinGroup, I own P4
  C4->>L: JoinGroup, I own nothing
  L-->>C4: Assignment is P5
  Note over C4: Start reading P5
Figure 5: a cooperative rebalance in two rounds. Only P5 changes hands, and only P5 is ever idle.
1

Round 1: everyone reports what they own

Each member sends JoinGroup carrying the partitions it currently owns. Nobody revokes anything yet. Consumers 1, 2 and 3 keep reading.

2

The leader plans the balanced result

With four consumers and six partitions, the fair split is two, two, one and one. The sticky rule finds the smallest change: move a single partition, say P5, from Consumer 3 to Consumer 4.

3

The leader withholds the moving partition

P5 is currently owned, so it cannot be given to Consumer 4 yet. The plan for this round leaves P5 out of everyone’s assignment. Consumers 1 and 2 receive exactly what they already had.

4

Consumer 3 notices the loss

Its new assignment is P4 only. It compares that with what it owned and realises it must let go of P5. It calls its revoke callback for P5 alone, commits the offset for P5, and stops reading it.

5

Round 2: the free partition is given away

Consumer 3 asks to rejoin, triggering another rebalance. Now P5 has no owner, so the leader gives it to Consumer 4. Its assign callback fires for P5 only.

6

Stable again

Consumer 4 fetches the committed offset for P5 and starts reading. The group has four members, and five of six partitions were never interrupted.

Counting the cost

Assume each round takes about three seconds to complete. With the eager protocol, all six partitions are idle for roughly one round plus cleanup, say four seconds, giving 24 partition-seconds of lost processing. With the cooperative protocol, only P5 is idle, for about two rounds, six seconds, giving 6 partition-seconds. The moved partition waits a little longer, but the total loss is a quarter as large, and the five other partitions never stopped at all.

Measure (illustrative)EagerCooperative sticky
Partitions revoked61
Partitions idle at any point61
Seconds idle for the moved partitionAbout 4About 6
Seconds idle for an unmoved partitionAbout 40
Total partition-seconds of idlenessAbout 24About 6
Consumers that clear their local state4 of 41 of 4
i
Bigger groups gain more

With 3 consumers and 6 partitions the gain is modest. With 60 consumers and 600 partitions, one new member moves about ten partitions at most, while the eager approach halts all 600. The larger the group, the more valuable the cooperative approach becomes.

LScaling In: When a Consumer Leaves or Crashes

Members do not only join. When one leaves, its partitions must find new owners. The cooperative approach handles this with fewer rounds, since nobody has to give anything up first.

Suppose Consumer 4 owns P5 and now leaves. There are two ways this can happen, and they differ mainly in how long it takes to notice.

Clean leave

The consumer says goodbye

When a consumer is closed properly, it sends a LeaveGroup request. The coordinator starts a rebalance immediately, and the partitions are reassigned within about one round.

Crash

The consumer just disappears

The coordinator notices only when heartbeats stop for longer than the session timeout, which is 45 seconds by default in modern versions. Only then does the rebalance begin.

Why one round is enough

When the owner of P5 is gone, P5 has no owner. The rule “never hand over an owned partition in the same round” does not apply, because there is nothing to take away. The leader computes the fair split for three consumers, sees that P5 is free, and assigns it directly. The remaining consumers keep what they already have. The result: one round, no revocations, and no one but the departed member’s partitions is affected.

StepClean leaveCrash
Time until the coordinator reactsAlmost immediateUp to the session timeout
Partitions idle while waitingP5 only, brieflyP5 for the whole detection time
Partitions revoked from survivorsNoneNone
Rounds needed11
Where the biggest delay comes fromThe rebalance itselfWaiting to detect the failure
Everyday analogy

A waiter finishes their shift and tells the manager, who hands the tables to a colleague at once. In the crash case, a waiter collapses in the back room and nobody realises for a while. Their tables sit unserved until someone notices. Once someone does, handing the tables over takes a minute.

What survivors experience

For a survivor, the rebalance is almost invisible. It sends JoinGroup with its owned partitions, receives an assignment that includes everything it already had plus, possibly, a new partition, and continues. Its assign callback fires only for the newly added partition. Under the eager protocol, in contrast, every survivor would have stopped, revoked everything, and received partitions again.

!
Detection time is often the biggest cost

In a crash, most of the disruption is the waiting, not the rebalance. Lowering the session timeout shortens it, but too low a value causes healthy consumers to be evicted after a brief pause, such as a garbage collection stall. Choose deliberately.

IInside the Assignment Algorithm

Now that the behaviour is clear, let us look at how the group leader actually computes a sticky, cooperative plan.

The work happens in the leader’s process, inside the assignor code. It runs each time a rebalance completes its join phase. The input is the list of members, their subscriptions, the partition lists of the subscribed topics, and, for the cooperative case, what each member says it currently owns.

1

Collect current ownership

Each member’s subscription includes the partitions it owned and the generation in which it received them. The leader uses this as the picture of the “old” assignment.

2

Resolve conflicting claims

If two members both claim the same partition, the claim from the newer generation wins. If they cannot be told apart, the claims are ignored and the partition is treated as free. This keeps stale members from confusing the plan.

3

Compute fair quotas

Divide the number of partitions by the number of members. Everybody gets the rounded-down number, and the remainder members get one extra.

4

Keep what is already in the right place

Each member keeps its current partitions, up to its quota. Members above their quota mark the surplus as movable.

5

Fill the gaps

Free partitions and movable surplus partitions are given to members below their quota, until every member is within one of each other.

6

Apply the cooperative rule

Any partition that is currently owned and would change owner is removed from the result. It will be assigned in the next round, after its old owner has let it go. Partitions that were free are assigned now.

A second example with a remainder

Ten partitions and four consumers give a quota of two each, with two consumers taking three, since ten divided by four is two remainder two. Suppose the current owners hold 3, 3, 2 and 2 partitions. Already balanced, so nothing needs to move. Now suppose the current split is 5, 3, 2, 0 because of earlier changes. The first consumer is over its quota of three by two, and the last is under its quota of two by two. The leader moves exactly two partitions from the first to the last, and keeps all the rest where they are.

ConsumerOwned nowTargetAction in round 1Action in round 2
A53Revoke 2 partitionsNone
B33NoneNone
C22NoneNone
D02None, must waitReceive 2 partitions
i
Balance is by count, not by load

The assignor counts partitions and does not know how busy each one is. If one partition carries ten times the traffic of the others, the consumer holding it will be overloaded even though the counts are equal. Uneven partition load is a design problem that no assignor can fully remove.

!
Who runs it matters

The classic protocol runs the algorithm inside one client, the group leader. If that client is slow, or its clock or memory is under pressure, the whole rebalance waits. Groups with thousands of partitions and many members make this a real cost, and are one reason a server-side protocol was later introduced.

GGenerations, Member IDs and Fencing

A rebalance changes who owns what. To stop confused or slow members from acting on old ownership, Kafka numbers every round.

Each successful rebalance produces a new generation, a counter that goes up by one. Each member also receives a member id when it first joins. Every important request the member sends, including heartbeats and offset commits, carries its member id and the generation it believes is current. The coordinator compares this with the truth and rejects requests that do not match.

Everyday analogy

Each table plan is printed with a version number. A waiter who tries to collect payment for a table using an old plan is politely turned away by the cashier, who only accepts the current version. That prevents a waiter who was away from serving a table that now belongs to someone else.

ErrorPlain meaningTypical causeWhat the client does
REBALANCE_IN_PROGRESSA rebalance is under way. Please rejoinAnother member joined or leftRejoin the group
ILLEGAL_GENERATIONYour generation number is out of dateYou missed a rebalanceRejoin and treat old ownership as lost
UNKNOWN_MEMBER_IDThe coordinator does not know youSession timed out and you were removedRejoin as a new member
FENCED_INSTANCE_IDAnother member uses your static idDuplicate instance idsShut down, since two instances share one id
Commit failed exceptionYou tried to commit after losing membershipSlow processing or long pauseExpect duplicates on the new owner

Revoked versus lost

Kafka distinguishes two situations. A partition is revoked in an orderly way: the member still belongs to the group, so it can safely commit its offsets first. A partition is lost when the member has already been removed, for example after a session timeout. In that case the partition may already belong to someone else, and committing offsets would be wrong, since the new owner is reading from the last good commit.

The cooperative design added a separate callback for this second case, named onPartitionsLost. Its default behaviour is to call the revoke callback, which keeps older code working. But good code separates the two: commit in revoke, and only clean up local state, without committing, in lost.

i
Fencing also protects transactions

Exactly-once processing with transactions uses the same idea. When a consumer’s output and offset commits are part of one transaction, the group’s generation information is passed along, so a stale member cannot commit work for partitions that now belong to another member.

IInside the Consumer: The Poll Loop and Its Threads

The poll call is the engine of a consumer. Understanding what it does explains many rebalance surprises.

A Kafka consumer is not a background service that pushes records to you. It is a library driven by your own calls to poll. Inside that call, the client fetches records, checks whether a rebalance is needed, sends and receives group protocol messages, and calls your listener callbacks. Only one thread, the one calling poll, does this work. A second, hidden thread does nothing but send heartbeats.

Your thread

The poll thread

Fetches records, drives the rebalance protocol, runs your callbacks, and commits offsets. If it is busy elsewhere, none of this happens.

Hidden thread

The heartbeat thread

Sends a heartbeat every few seconds so the coordinator knows the process is alive, even while your code is busy processing.

What this means for rebalances

1

The heartbeat thread learns of a rebalance

A heartbeat reply says the group is rebalancing. The thread cannot act on it alone; it only sets a flag.

2

The poll thread notices at its next poll

Until your code calls poll again, nothing happens. This is why slow processing delays a rebalance for everyone waiting on you.

3

Callbacks run inside poll

Revoke, assign and lost callbacks all run on your polling thread, one at a time. Long work in a callback delays the join, exactly as slow processing does.

4

The join completes and polling resumes

After the assignment arrives, poll returns records again, now for the updated set of partitions.

Cooperative details that matter

Under the cooperative protocol, poll can keep returning records for the partitions you retain while the rebalance is in progress. Partitions that were paused by your code stay paused if you keep them, because the consumer never gave them up. Under the eager protocol, in contrast, pause state was lost on every rebalance, since every partition was revoked and reassigned. Code that pauses partitions for back-pressure should therefore be reviewed when the protocol changes.

!
Keep callbacks quick

Callbacks run while the whole group may be waiting for you. Commit and clean up, but do not perform slow network calls or long flushes there. If heavy cleanup is unavoidable, hand it to another thread and return promptly.

i
Thread safety

The consumer object is not safe to use from several threads at once. Do all polling, committing and callback work on one thread, and use the provided wake-up method if another thread needs to interrupt a poll.

RThe Rebalance Listener: What Your Code Sees

Your application can register a listener that is called around rebalances. Cooperative rebalancing changes what the callbacks mean, and code that ignores this can break.

The listener has three callbacks. One runs when partitions are taken away, one when partitions are added, and one when they are lost. In the eager protocol, these are simple: the first callback receives everything you owned, and the second receives everything you now own. In the cooperative protocol, they receive only the difference.

CallbackEager: what it receivesCooperative: what it receives
Partitions revokedAll partitions you owned, at the start of every rebalanceOnly the partitions being taken from you, possibly none
Partitions assignedYour complete new assignmentOnly the partitions newly added to you, possibly none
Partitions lostSame as revoked by defaultPartitions you no longer own after losing membership
Everyday analogy

In the eager version, the manager says, “Here are all your tables.” In the cooperative version, the manager says, “You get one more table, number five,” and expects you to remember the other four yourself. A waiter who forgets their existing tables would go looking for them.

Common mistakes with listeners

Bug

Treating “assigned” as the full set

Code that resets all state or seeks all partitions in the assign callback will wipe work for partitions that stayed put. Ask the consumer for its full current assignment instead.

Bug

Clearing everything on revoke

Code that empties every cache in the revoke callback throws away state for partitions that the member keeps. Remove only the entries for the partitions listed.

Bug

Assuming the callback always has items

Under the cooperative protocol, both callbacks may be called with empty lists during a rebalance. The code must handle that gracefully.

Bug

Committing in the lost callback

The member no longer belongs to the group, so a commit fails, or worse, overwrites the new owner’s progress in edge cases. Commit only in revoke.

A safe way to think about it

Keep a simple mental model: your set of owned partitions is a set. The revoke callback removes items from it. The assign callback adds items to it. Anything you keep per partition, such as a cache, a counter or a buffer, should be attached to that set and updated exactly when the set changes. If your code follows that model, it works correctly under both protocols, and you can switch between them without rewriting anything.

!
Update the listener before switching protocols

Old listener code often quietly relies on eager behaviour. Review and test it under cooperative rules before you change the assignor in production. It is the single most common source of surprises during a migration.

DCommitting Offsets Safely During Rebalances

Handovers are exactly where duplicates and gaps sneak in. A careful commit strategy keeps them under control.

When a partition moves, the new owner starts reading from the last committed offset. Everything the old owner processed after that commit will be read again. So the closer the last commit is to the old owner’s real position, the fewer duplicates appear. This is why the revoke callback is the right place to commit: it runs just before the partition leaves, while the member still has the right to commit.

1

Finish or bound the current batch

Before returning from the poll loop, complete the work for the records already received, so that what you commit truly reflects work done.

2

Commit only the revoked partitions

In the revoke callback, commit offsets for the listed partitions synchronously. The commit must succeed before the partition is handed on.

3

Drop local state for those partitions

Remove caches and buffers for the revoked partitions only. Keep everything else.

4

Let the new owner start from the commit

The next owner reads the committed offset and continues. Any work done after the commit is repeated, which is normal under at-least-once processing.

Automatic versus manual commits

Automatic commits

  • Simple: the client commits on a timer and before rebalances
  • Good for tolerant, idempotent processing
  • Less control over exactly what has been committed

Manual commits

  • Exact control: commit after the work is truly done
  • Required for careful handling in the revoke callback
  • More code to write and test
  • Easy to forget partitions or over-commit

Exactly-once processing

When a consumer reads, transforms and writes results back to Kafka, transactions can make the output and the offset commit one atomic step. For this to be safe when partitions move, the group’s generation information must be included with the offset commit, so that the broker can refuse a commit from a member that no longer owns the partition. With the cooperative protocol, this fencing is what prevents a slow old owner from committing work after the partition has been given away.

i
Make processing idempotent anyway

Even with careful commits, some duplicates can occur after crashes. Design handlers so that processing the same message twice does no harm, for example by using unique ids and upserts. It is the most reliable defence, and it makes rebalancing far less scary.

TTiming Settings That Control Rebalances

A few time limits decide how quickly a failure is noticed and how easily healthy consumers are mistaken for dead ones.

SettingWhat it controlsTypical default
session.timeout.msHow long the coordinator waits for a heartbeat before declaring the member dead45 seconds in modern versions
heartbeat.interval.msHow often a heartbeat is sent. Should be well under the session timeout, commonly a third or less3 seconds
max.poll.interval.msThe longest allowed gap between two poll calls, also the time other members wait for you to rejoin5 minutes
max.poll.recordsHow many records one poll may return, which bounds how long a batch can take500
group.initial.rebalance.delay.ms (broker)A short wait when a new group forms, so several starting members can be grouped into one rebalanceAbout 3 seconds

Two clocks, two failure types

The consumer runs a background thread that sends heartbeats, separate from the thread that processes records. The session timeout watches the background thread: if the whole process freezes or the network is cut, heartbeats stop. The poll interval watches the processing thread: if your code takes too long between polls, the consumer is considered stuck, even though heartbeats are still flowing. Either clock running out causes the member to be removed and a rebalance to begin.

Everyday analogy

A waiter is expected to wave to the manager every few seconds to show they are alive. That is the heartbeat. They are also expected to return to the kitchen for new orders at least every few minutes. That is the poll interval. A waiter who keeps waving but never comes back to the kitchen is still treated as stuck.

How the settings interact with rebalancing

Waiting

Slow members delay the join phase

The coordinator waits for members to rejoin, up to the rebalance timeout, which equals the poll interval for classic consumers. A member busy in long processing holds others back until it returns to poll.

Detection

Long session, slow failure detection

A very long session timeout means a crash goes unnoticed for a long time, and its partitions sit idle.

False alarms

Short session, wrongful evictions

A very short timeout evicts consumers that only paused briefly, such as during garbage collection, causing needless rebalances.

Batch size

Big batches risk the poll limit

If 500 records take longer than the poll interval to process, the member is dropped. Lower the batch size or raise the interval.

i
A useful check

Estimate the slowest realistic time to process one batch, add a generous margin, and make sure it is comfortably below the poll interval. If it is not, reduce the batch size before touching any other setting.

MStatic Membership: Avoiding Rebalances Altogether

Cooperative rebalancing makes rebalances cheap. Static membership tries to make many of them disappear.

Normally, a restarting consumer looks like a stranger: it leaves, or disappears, and a new member with a new member id joins. That triggers two rebalances. With static membership, each consumer is given a stable name through the setting group.instance.id. When an instance with a known name restarts and rejoins within the session timeout, the coordinator recognises it, hands back its previous assignment, and does not start a rebalance at all.

Everyday analogy

A waiter with a name badge steps out for a cigarette break. Because the manager knows exactly who they are and when they will be back, the tables stay with them. Without a badge, the manager would treat the waiter as gone and reshuffle everything on the floor.

0
rebalances if a static member returns within the session timeout
Unique
every instance needs its own instance id
Longer
session timeouts are usually raised alongside it

How it combines with cooperative rebalancing

The two features cover different situations. During a planned restart that finishes inside the session timeout, static membership avoids any rebalance. During an unplanned, longer outage, or when capacity is truly added or removed, a rebalance is unavoidable, and the cooperative protocol makes it as gentle as possible. Used together, they turn most deployments into non-events and leave only the truly necessary movements.

Benefits

  • Rolling restarts cause no rebalances if they are quick enough
  • Local state and caches stay warm
  • Lag stays flat during deployments

Trade-offs

  • Real failures are noticed only after the session timeout
  • Duplicate instance ids cause the older instance to be fenced
  • Needs stable identifiers, such as pod names
  • A departed instance’s partitions stay idle until the timeout
!
Scaling down needs care

If you permanently remove a static member, its partitions stay reserved for it until its session times out. Plan removals so that a long timeout does not leave partitions unread for minutes.

KRolling Out on Container Platforms

Most consumer fleets today run in containers. How the platform stops and starts instances decides how gentle each rebalance is.

Container platforms start and stop instances in an orderly way, but their defaults do not know about Kafka groups. Three details make the biggest difference: how the instance is told to stop, how much time it is given, and how quickly replacements start.

Stopping

Handle the termination signal

When the platform asks an instance to stop, the application should catch the signal, finish its current work, and close the consumer so it leaves the group cleanly.

Time

Allow a grace period

Give the instance enough time to commit and close. If the platform kills it too soon, the group must wait for the session timeout.

Names

Use stable identities

Workload types that give each instance a stable name make static membership straightforward, since the same name returns after a restart.

Pace

Roll out slowly

Limit how many instances are replaced at once, so the group can settle between steps and never loses many members together.

A rollout compared

PracticeEffect on the group
Instance killed without closing the consumerPartitions idle until the session timeout, then a rebalance
Instance closes the consumer on the termination signalImmediate LeaveGroup and a prompt rebalance
Many instances replaced at onceLarge moves, and a risk of falling behind
One or two instances replaced at a timeSmall moves that settle before the next step
Static membership with a stable nameNo rebalance if the replacement is back in time
Everyday analogy

Changing shifts in a restaurant goes best when the outgoing waiter hands over their tables and says goodbye, and the incoming waiter arrives a few minutes later. It goes badly when half the floor walks out at once, or when a waiter simply vanishes without telling anyone.

i
Match session timeout to restart time

If a typical restart takes about twenty seconds, a session timeout comfortably above that lets static members return before anyone notices. If restarts take minutes, either speed them up or accept the rebalance and let the cooperative protocol keep it small.

HChoosing and Configuring the Assignor

Turning the feature on is one setting. Getting the choice right takes a little more thought.

The consumer setting partition.assignment.strategy takes a list of assignor names in order of preference. The coordinator picks an assignor that every member supports. Among the supported ones, it uses the members’ order of preference to break ties. This makes it possible to run mixed lists safely during an upgrade, and it explains the default list in recent versions.

SituationSuggested listResult
New group, fresh applicationCooperative sticky aloneCooperative behaviour from the first rebalance
Existing group on range, upgrading step oneRange first, cooperative sticky secondGroup stays on range and eager, upgrade path is open
Existing group, upgrading step twoCooperative sticky aloneGroup moves to cooperative behaviour
Need co-partitioned topics on the same consumerRangePartition numbers line up across topics

When not to use a sticky assignor

Sticky assignors do not promise that partition 3 of topic A and partition 3 of topic B end up on the same consumer. Applications that join two topics by key, relying on the range assignor’s alignment, will break silently if switched. Frameworks such as Kafka Streams use their own assignment logic that keeps co-partitioned tasks together, so this concern mostly applies to hand-written consumers.

Choose

Cooperative sticky

For most consumer groups with stateful handling, long deployments, or many partitions. It is the modern general-purpose choice.

Choose

Range

When you rely on partition alignment across topics in your own code, and your group is small or quiet enough that eager pauses are acceptable.

Rarely

Round robin

When the most even spread matters more than stability, such as many small independent topics with no state.

Avoid

Plain sticky for new work

It has the stability without the non-stop behaviour, so it is mostly a stepping stone for older groups.

i
Every member must agree

All members of a group must include at least one common assignor. If one deployment lists only an assignor that others lack, the join fails with an inconsistent-strategy error. Keep the list identical across every instance of the same application.

UUpgrading a Live Group From Eager to Cooperative

You cannot flip a switch on a running group, because old and new members would disagree on the rules. The safe path takes two rolling restarts.

flowchart TD
  A["Group runs an eager assignor"] --> B["Update client library and rebalance listener code"]
  B --> C["First rolling restart: list old and cooperative assignors, old first"]
  C --> D["Group keeps using the eager protocol"]
  D --> E["Second rolling restart: remove the old assignor"]
  E --> F["Group switches to the cooperative protocol"]
Figure 6: the two-step upgrade. The group changes protocol only when every member supports only the cooperative one.
1

Upgrade the client library

Every consumer must run a client version that knows the cooperative protocol, which means 2.4 or newer, and preferably a recent release with later fixes.

2

Make the listener cooperative-safe

Review the callbacks for the mistakes in Chapter 15. This code works under both protocols if written to handle differences.

3

First rolling restart

Configure each consumer with both assignors, keeping the old one first. The group still finds the old assignor as the only common preference until every member has the new list, and it keeps using eager rebalancing throughout.

4

Confirm every member has both

Check that no instance still runs with the old single-item list, for example a forgotten batch job or a canary.

5

Second rolling restart

Configure each consumer with the cooperative assignor only. As members restart, the group becomes one that supports only the cooperative protocol, and from then on rebalances are cooperative.

Watch for these problems

ProblemWhy it happensHow to avoid it
Group never becomes cooperativeOne member still lists an eager-only assignorFind and restart every member, including scheduled jobs
Listener bugs appear after step fiveCallbacks assumed full-set behaviourTest the listener under cooperative rules first
Extra rebalances during rolloutEach restart triggers a rebalance in the eager phaseRoll out in quiet hours, or use static membership
Rolling back is awkwardMembers with different lists disagree about protocolReverse the same two steps, and do not flip in one go
!
Do not skip the intermediate step

Switching every consumer straight to cooperative-only with a single restart is risky. During the rollout, old members and new members would have no assignor in common, and joins would fail. The two-step path exists precisely to avoid that gap.

FFour Failure Stories With Real Numbers

Definitions are useful, but the behaviour really sinks in when you follow numbers through a bad day. These four stories are illustrations built on realistic settings.

Story 1: the Friday deployment

A service runs as 40 instances reading a topic with 120 partitions, so each instance owns about three partitions. The group uses the range assignor, which is eager. The deployment restarts instances one at a time. Each restart causes a rebalance when the old instance leaves and another when the new one joins, so about 80 rebalances in total. Suppose each one takes about eight seconds of complete silence for all 120 partitions.

That is roughly 640 seconds, more than ten minutes, during which nothing is processed, spread across the release in small bursts. Backlogs build every time and are worked off afterwards, but downstream systems see repeated delays. After switching to the cooperative sticky assignor, each event moves only about three partitions, and the other 117 keep running. With static membership added, and each restart shorter than the session timeout, most of the 80 rebalances disappear entirely.

SetupRebalancesPartitions paused per rebalanceApproximate total pause
Eager rangeAbout 80120Around 10 minutes of full silence
Cooperative stickyAbout 80 (some with two rounds)About 3Small, per-partition pauses
Cooperative sticky plus static membershipFew or none0 for quick restartsClose to zero

Story 2: the slow member that held everyone up

One consumer in a group of 30 takes on an unusually large batch and spends four minutes processing it. The poll interval is five minutes, so it is not considered dead. At that moment another instance restarts, triggering an eager rebalance. Every member must revoke and rejoin, but the slow one cannot rejoin until it returns to poll. The other 29 members, having already revoked everything, sit waiting for up to the full poll interval.

Result: nearly four minutes of complete silence across the whole group, caused by one slow member. With the cooperative protocol, the other 29 keep processing the partitions they retain while the slow member finishes. Only the partitions that need to move wait for it, and they wait because their previous owner must release them, which is exactly as it should be.

i
What to learn

Long processing between polls is dangerous under either protocol, but cooperative rebalancing limits the damage to the affected partitions. Bounding batch time is still the real cure.

Story 3: the listener that forgot what it kept

A team keeps an in-memory cache of recently seen message ids per partition, used to drop duplicates. Their revoke callback clears the whole cache, and their assign callback rebuilds it from scratch for the partitions it receives. Under the eager protocol this works, since every rebalance revokes and reassigns everything.

After switching to cooperative, a rebalance revokes one partition but the callback still clears the entire cache. The assign callback then receives only the one new partition and rebuilds only that. The retained partitions keep running with empty caches, and nothing rebuilds them. Duplicates that used to be dropped now pass through for hours. The fix is small: remove only the revoked partitions’ entries in the revoke callback.

Story 4: the join that quietly stopped matching

Two topics, orders and payments, each have 12 partitions, keyed by customer, so a given customer lands in the same partition number in both. A hand-written consumer joins them in memory. With the range assignor, partition 3 of both topics always lands on the same consumer, so the join works. After the team moves to a sticky assignor, orders partition 3 stays on Consumer B, while payments partition 3 ends up on Consumer D after a scale-out. Now the two halves of the join sit on different consumers, and matches are missed.

The lesson is that sticky assignors do not promise co-partitioning. Applications that need it should use a framework that handles it, such as Kafka Streams, or keep an assignor that provides the alignment.

StoryRoot causeEffectCure
Friday deploymentEager rebalancing on every restartRepeated full stopsCooperative sticky, static membership
Slow memberLong work between pollsGroup-wide waitCooperative protocol, bounded batches
Forgetful listenerCallbacks assumed full-set behaviourSilent state lossUpdate only changed partitions
Broken joinAssumed alignment from rangeMissed matchesUse a framework, or keep range

NKafka Streams and Kafka Connect: Cooperative Cousins

Two of Kafka’s own frameworks use the same core idea, adapted to their own needs.

Kafka Streams

A Kafka Streams application divides its work into tasks, each tied to a partition (or a set of aligned partitions), and each task usually owns a local state store such as a table of running counts. Moving a task to another instance means rebuilding its state store from a log, which can take minutes or hours for a large store. This makes stickiness and gentle handover especially valuable.

Streams has its own assignor that keeps related partitions together and prefers to put tasks back where their state already lives. Recent versions also use a cooperative approach, and add a feature called warm-up replicas. Before a task moves to a new instance, the new instance quietly builds a copy of the state in the background. Once it has caught up, a follow-up rebalance, called a probing rebalance, moves the task over. The old owner keeps serving until then, so users see no pause. Settings such as the number of standby replicas, the acceptable recovery lag, the maximum number of warm-up replicas and the probing interval control this behaviour.

Everyday analogy

Before a new waiter takes over a table, they shadow the current waiter for a while, learning the guests’ preferences. Only when they are ready does the manager switch. The guests never notice a change.

Kafka Connect

Kafka Connect runs connectors and their tasks across a cluster of workers. Early versions used stop-the-world rebalancing, so adding a worker paused every connector in the cluster. A later improvement, incremental cooperative rebalancing for Connect, lets running tasks continue during a rebalance and moves only the ones that must move. It also adds a grace period, controlled by a setting for the scheduled rebalance delay, during which the tasks of a worker that has just disappeared are not immediately handed out. If the worker comes back quickly, for example after a restart, it simply picks up its tasks again.

FrameworkUnit that movesExtra feature for smoothnessCost of a move
Plain consumerPartitionStatic membershipCache warm-up, possible duplicates
Kafka StreamsTask with stateStandby and warm-up replicasState restore from a changelog
Kafka ConnectConnector taskScheduled rebalance delayTask restart and reconnect
i
One idea, three flavours

In all three, the message is the same: do not stop what does not need to stop, and never hand off something that is still being used. Learning the consumer version makes the framework versions easy to understand.

VThe Next Generation: The Server-Side Protocol

Cooperative rebalancing softened the classic protocol. A newer protocol redesigns the conversation so that most of the pain never appears.

Recent Kafka releases, with general availability in Kafka 4.0, introduce a new consumer group protocol (proposal number KIP-848). It is selected on the client with the setting group.protocol, and the older behaviour is still available under the name classic. The new protocol moves work from the clients to the brokers and removes the group-wide barrier.

AspectClassic protocolNew protocol
Who computes the assignmentOne client, the group leaderThe group coordinator on the broker
Join and sync barrierAll members wait for each otherNone. Members reconcile independently
Cooperative behaviourOptional, through the assignorBuilt in, by design
HeartbeatOnly says “I am alive”Also carries subscription and ownership, and returns the target assignment
Where the assignor livesIn client code and configurationOn the broker, chosen by name
Some timeoutsSet on each clientSet on the broker for the whole group

How it works in outline

1

Members heartbeat with their state

Each heartbeat tells the coordinator what the member subscribes to and which partitions it currently owns.

2

The coordinator computes a target assignment

Whenever the group changes, the broker runs a server-side assignor and records the result. This does not stop anyone.

3

Members learn their target in heartbeat replies

Each member is told what it should own, and gradually reconciles its actual state with the target.

4

Partitions are released first, then acquired

A member gives up partitions it should no longer hold. Only after they are released does the coordinator let the new owner claim them, the same safety rule as before, without a global pause.

Everyday analogy

Instead of gathering all waiters for a meeting, the manager keeps a live board of table plans. Each waiter glances at it between customers, hands over a table when the board says so, and picks up a new one once it is free. Nobody has to wait for everyone else.

!
Check your version and feature support

The new protocol changes which client settings apply and which assignors are available, and some features are added in later releases. Read the documentation for your exact Kafka version before migrating. The classic protocol with cooperative sticky remains fully supported and is what most existing systems run today.

PPerformance and Scalability

Rebalancing costs time and effort. Understanding where the cost comes from tells you which knobs are worth turning.

The cost of a pause

A pause is not only the seconds lost. While a partition is idle, messages keep arriving, so a backlog builds. After the pause, consumers must work off that backlog on top of the normal flow. The recovery time depends on how much spare capacity they have.

5,000
messages per second arriving
10 s
pause, so a 50,000 message backlog
20 s
to clear it if consumers can do 7,500 per second

The arithmetic is simple. A ten-second pause at 5,000 messages per second creates a backlog of 50,000. If the consumers can process 7,500 per second, they have 2,500 per second to spare, so it takes 20 seconds to catch up. If they only had 500 per second to spare, it would take 100 seconds. Groups that run close to capacity feel every pause much longer than the pause itself, which is one more reason to keep headroom.

Where the rounds spend their time

Waiting

The join phase

The coordinator waits for members to report in. A slow member or a busy processing loop stretches this phase more than anything else.

Computing

The assignor run

Grows with the number of partitions and members. It is usually fast, but with thousands of partitions and hundreds of members it is measurable.

Messaging

The subscription data

Cooperative members also send lists of owned partitions, so the messages grow with the number of partitions per member.

Cleanup

Callbacks and commits

Revoke callbacks that commit or flush data add their own delay before a partition can be released.

Trade-offs of two rounds

The cooperative protocol sends more requests than the eager one, because a move needs a second round. It also makes moved partitions wait somewhat longer. In exchange, all other partitions run without interruption. For most groups, this is a clear win. For a very small group with no state, where a rebalance takes a fraction of a second, the difference may be too small to notice.

Where cooperative pays off

  • Large groups with many partitions
  • Consumers that keep local state or caches
  • Frequent deployments and autoscaling
  • Latency-sensitive downstream systems

Where the gain is small

  • Tiny groups with fast rebalances
  • Stateless consumers on quiet topics
  • Groups that almost never change
  • Workloads where a few seconds of delay is harmless
i
Headroom is the cheapest fix

A group that runs at sixty percent of capacity recovers from any pause quickly. A group at ninety-five percent never really catches up. Capacity planning and rebalance tuning go together.

CConsistency, Duplicates and Delivery Guarantees

What can and cannot go wrong for your data when partitions change hands? The answer depends on the two rules the protocol enforces.

Two rules govern every rebalance. First, at most one owner at a time for each partition in a group. The cooperative design keeps this by always leaving a moved partition unowned for a round. Second, the new owner starts from the last committed offset. Together, these give you ordered processing per partition and at-least-once delivery, with duplicates limited to the work done since the last commit.

GuaranteeHolds during a rebalance?Why
Order within a partitionYesOne owner at a time, and the new one continues from the commit
No message is skippedYes, with careful commitsOffsets are committed only after processing
No message is repeatedNoWork done after the last commit is read again by the new owner
Only one owner at any momentYesThe gap round and generation fencing prevent overlap
Exactly-once resultsOnly with transactions or idempotent handlingNeeds atomic output and offset commits

How the cooperative protocol changes the duplicate window

In the eager protocol, every partition is handed over, so every partition has a possible duplicate window. In the cooperative protocol, only moved partitions do. The window is the same size for a moved partition, but the number of affected partitions is smaller. That lowers the total duplicates without changing the guarantee in any one place.

Everyday analogy

When a table changes waiter, the new waiter checks the last order slip that was signed off. Anything the old waiter had taken but not yet signed may be taken again. It is safer to ask a guest twice than never to ask.

!
Do not confuse “one owner” with “no duplicates”

The single-owner rule prevents two consumers reading a partition at the same time. It does not prevent the same message being processed twice at different times. Idempotent handlers are still the right defence.

SSecurity Around Consumer Groups

Anyone who can join a group can disturb it. Group membership deserves the same care as topic access.

To join a group and commit offsets, a client needs permission to read the topic and permission to use the group itself. Because every join, leave and even a badly behaved member can trigger a rebalance, a careless access policy lets an accidental or malicious client interrupt everyone in the group.

Access

Restrict who may use a group

Give each application permission only on its own group, by exact name or a well-chosen prefix, so that others cannot join it or commit offsets in its name.

Identity

Authenticate every client

Use TLS and a login method so that group members are known identities, not anonymous connections.

Limits

Cap the group size

A broker setting can limit how many members a group may have, which protects against a runaway deployment flooding the coordinator with joins.

Static ids

Guard instance ids

A client that reuses a static instance id fences out the real instance. Access rules on the group protect against this kind of takeover.

Rebalance storms as an availability risk

A buggy client that joins and leaves in a loop, or a mis-scaled autoscaler that adds and removes instances every minute, keeps a group permanently rebalancing. Even with cooperative rebalancing, constant motion prevents the group from settling. Monitoring the rebalance rate, and setting alerts on it, is as much a reliability control as a security one.

!
Wide-open group permissions

Rules that allow any client to use any group are convenient in development and risky on shared clusters. Prefer explicit, narrow permissions, and review them when applications are retired.

MMonitoring Rebalances

You cannot tune what you cannot see. A handful of consumer-side and group-side signals tell you whether rebalancing is healthy.

SignalWhat it tells youWhat to do
Rebalance count and rate per hourHow often the group reshufflesInvestigate anything that is not tied to a deployment or scaling event
Rebalance latency, average and maximumHow long each rebalance takes from the client’s viewLong maxima point to slow members or slow callbacks
Failed rebalancesRebalances that did not completeLook at session and poll timeouts
Time since last rebalanceWhether the group is stableVery short values mean churn
Assigned partition count per memberHow balanced the group isLarge differences mean something is stuck or skewed
Longest gap between pollsHow close members are to the poll limitIf near the limit, shrink batches or speed up processing
Heartbeat response timeHealth of the path to the coordinatorRising values suggest coordinator or network trouble
Consumer lag per partitionEffect on the businessSpikes after each rebalance show the cost of pauses
Group stateStable, preparing or completing a rebalanceLong time outside Stable needs attention

Reading the logs

Coordinator logs on the broker record why a rebalance began: a new member, a member leaving, a member failing to heartbeat, or a subscription change. Client logs include messages when a consumer leaves because the time between polls exceeded the limit. Reading these two sources together answers the most important question: what triggered this rebalance?

1

Find the trigger

Look at the broker’s coordinator log around the time of the lag spike. Did a member join, leave, or time out?

2

Check for timeouts

If a member timed out, check whether it missed heartbeats, which suggests a frozen process or network trouble, or exceeded the poll interval, which suggests slow processing.

3

Measure the rounds

Compare rebalance latency with the time between polls. If most of the time is spent waiting for one member, that member is the problem.

4

Confirm the protocol

If pauses affect every partition, the group may still be eager. Check that every member lists only the cooperative assignor.

i
Metric names vary by version

Exact metric names change between client versions and between protocols. Build dashboards around the ideas above and confirm current names in the documentation for your version.

RReal-World Usage

Teams that run large consumer fleets tend to reach cooperative rebalancing for the same reasons, whatever their industry.

Kafka Connect clusters

The Kafka community, in public design discussions and vendor engineering write-ups, described how stop-the-world rebalancing in Connect meant that adding or restarting one worker paused every connector in a cluster. Incremental cooperative rebalancing was introduced so that healthy tasks kept running while only the necessary ones moved, and so that a restarting worker had a short grace period to reclaim its tasks.

Stateful stream processing

Applications that keep large local state, such as running aggregates and lookup tables, suffer most from unnecessary handovers, since each moved task must rebuild its state. The Kafka Streams community has described sticky task placement, standby copies and warm-up replicas as the main tools for making scale-outs and restarts painless for these applications.

Autoscaled consumer fleets

Organisations that run consumers on container platforms scale them up and down with traffic, and roll out new versions many times a day. Every one of those events is a rebalance. Combining cooperative sticky assignment with static membership is a commonly recommended pairing for this kind of environment, since it removes most rebalances and softens the rest.

An illustration: an order processor on sale day

This is a simplified illustration, not a description of any specific company. A retailer expects ten times its usual traffic and has its order processor autoscale from 20 to 100 instances over an hour. With eager rebalancing, each step of the scale-up stops all 200 partitions for several seconds, and the backlog compounds just when demand peaks. With cooperative sticky assignment, each step moves only the handful of partitions needed to give the new instances a fair share, and the existing instances keep serving orders throughout.

i
The common thread

Every one of these teams found the same thing: the problem was not the rebalance itself but the fact that everyone had to stop for it. Removing the need to stop was the real gain.

DDesign Patterns and Anti-patterns

A few habits make cooperative rebalancing shine. A few shortcuts quietly undo its benefits.

Patterns that work

Pattern

Cooperative sticky by default

Use it for every new consumer group, unless you have a specific reason such as partition alignment across topics.

Pattern

Static membership with stable names

Give each instance a fixed id derived from its pod or host name, and raise the session timeout to cover normal restarts.

Pattern

Delta-based listeners

Keep per-partition state in a structure that is updated only for the partitions named in each callback.

Pattern

Bounded batches

Size each poll so that its processing time stays far below the poll interval, leaving room for slow days.

Pattern

Idempotent handlers

Design processing so that repeating a message is harmless. This turns handover duplicates into a non-event.

Pattern

Gradual autoscaling

Scale in steps with cooldown periods, so the group settles between changes instead of chasing every spike.

Anti-patterns to avoid

ANTI-PATTERN 01Avoid
What people do

They switch to the cooperative assignor but leave a listener that clears all state on revoke and rebuilds only what is assigned.

Why it hurts

Under the cooperative protocol the callbacks carry only differences, so retained partitions lose their state and never get it back. Errors appear as silent data problems, not crashes.

Do this instead

Rewrite the callbacks to touch only the partitions they name, and test rebalances with scale-out, scale-in and crash scenarios before rollout.

ANTI-PATTERN 02Avoid
What people do

They do long, unbounded work inside the poll loop, such as a slow external call per record with no timeout.

Why it hurts

The member exceeds the poll interval, is removed, and triggers a rebalance. On return it rejoins and may be removed again, creating a loop of rebalances.

Do this instead

Set timeouts on external calls, lower the batch size, or hand work to a separate pool while the polling thread keeps calling poll.

ANTI-PATTERN 03Avoid
What people do

They configure an autoscaler to add and remove instances every minute based on instantaneous lag.

Why it hurts

The group is always mid-rebalance. Each scale event causes moves, which cause more lag, which triggers more scaling.

Do this instead

Scale on smoothed metrics, add cooldown periods, and prefer fewer, larger steps with headroom built in.

ANTI-PATTERN 04Avoid
What people do

They change every consumer straight to cooperative-only in a single restart.

Why it hurts

During the rollout, old and new members have no assignor in common, joins fail, and the group can stall entirely.

Do this instead

Follow the two-step upgrade: list both assignors first, then remove the old one after every member has been updated.

BBest Practices and Common Mistakes

A checklist for design reviews, and a table of mistakes that keep returning.

1

Use a recent client version

Later releases fixed several subtle cooperative rebalancing bugs. Running the latest supported client is cheap insurance.

2

Test the listener under every scenario

Exercise scale-out, scale-in, crash and restart cases, and confirm that per-partition state is correct after each.

3

Commit in the revoke callback

Commit offsets for exactly the partitions being released, and do not commit in the lost callback.

4

Keep the timeout family consistent

Heartbeat interval well below session timeout, and worst-case batch time well below the poll interval.

5

Keep assignor lists identical

Every instance of an application should have the same list, managed from one shared configuration.

6

Track rebalances as a first-class metric

Show the count, duration and trigger of rebalances on the same dashboard as lag, and alert on unexpected churn.

Common mistakeWhat goes wrongBetter approach
Assuming sticky means non-stopThe plain sticky assignor still stops everybodyUse the cooperative sticky assignor
Treating the assign callback as the full setState is lost or rebuilt wrongly for kept partitionsUpdate only the named partitions
Setting the session timeout very lowHealthy consumers are evicted during short pausesUse the default unless measurements say otherwise
Ignoring the poll intervalSlow batches cause evictions and loopsBound batch time and size
Reusing static instance idsInstances fence each other outDerive ids from unique host or pod names
Expecting even load from even countsHot partitions overload one consumerFix key distribution or add partitions thoughtfully

MMyths, Traps and Gotchas

Some ideas about rebalancing sound sensible and are wrong. These are the ones that cause the most confusion.

Myth

“Cooperative means no pauses”

Partitions that move still pause, across two rounds. What disappears is the pause for partitions that stay where they are.

Myth

“Sticky guarantees fair load”

It guarantees an even count of partitions, not an even amount of work. Busy partitions still overload their owners.

Myth

“The broker decides the assignment”

In the classic protocol one client, the group leader, computes it. Only the newer protocol moves this to the broker.

Myth

“Static membership removes all rebalances”

It removes those caused by quick restarts. Real scaling changes, long outages and subscription changes still rebalance.

Myth

“More consumers than partitions is fine”

The extra consumers sit idle, yet they still take part in rebalances. Match the group size to the partition count.

Myth

“A rebalance means something broke”

Rebalances are normal and expected. Only unexplained, repeated or very long ones deserve investigation.

Myth

“Committing in the lost callback is safe”

The member no longer belongs to the group at that point, so the commit is refused or harmful. Commit in revoke instead.

Myth

“Any mix of assignors works”

Members need at least one assignor in common, and the group picks from what all members support.

Gotchas worth remembering

!
The first rolling restart is still eager

During step one of the upgrade, the group keeps using the old protocol. Do not expect any benefit until the second step is complete, and do not judge the feature before then.

!
Callbacks may be called with nothing

Under cooperative rebalancing, the revoke and assign callbacks can be invoked with empty lists. Code that assumes at least one partition will fail on the empty case.

!
Adding partitions triggers a rebalance too

Increasing a topic’s partition count changes the assignment, and can also change which partition a key maps to on the producer side. Plan such changes with both effects in mind.

PPutting It Together: Settings for Three Scenarios

Let us apply what we have learned to three different workloads. The method matters more than the exact numbers.

Ask four questions of any consumer group. How much state does each consumer hold? How often do instances change? How long can a partition safely sit idle? And how heavy is the processing per batch? The answers point to different choices.

Scenario (illustration)Main concernAssignor and membershipTiming choices
Stateless order processor, autoscaledFrequent joins and leavesCooperative sticky, static ids where the platform gives stable namesDefault session timeout, modest batch size, cooldown on scaling
Stateful aggregator with large cachesCost of moving stateCooperative sticky with static membership, or Streams with standbysLonger session timeout to cover restarts, careful revoke handling
Small internal batch consumerSimplicityDefault settings may be enoughBounded batches, default timeouts
1

Measure the current pain

Count rebalances per day, their durations, and the lag spikes after each. Without a baseline, you cannot tell whether a change helped.

2

Choose the assignor

Cooperative sticky for most groups. Keep range only when partition alignment across topics is essential.

3

Decide on static membership

Use it if instances restart often and have stable names, and accept the slower detection of real failures.

4

Set the timeout family

Choose session and poll settings from measured worst cases, with generous margins.

5

Rehearse and compare

Run a scale-out, a crash and a rolling restart in a test environment, then compare the numbers with the baseline.

A quick sanity check for any design

Ask aloud: “If one instance restarts right now, which partitions pause, for how long, and what happens to their cached state?” If the team can answer with specific numbers, the group is understood. If the answer is “everything pauses for a bit”, the group is probably still eager and worth a closer look.

FFrequently Asked Questions

Short answers to the questions that come up again and again.

Q1What is the difference between sticky and cooperative?
Sticky describes which assignment is chosen: the one that changes the fewest partitions while staying balanced. Cooperative describes how the change is made: in stages, without revoking everything first. The cooperative sticky assignor does both.
Q2Does cooperative rebalancing avoid rebalances?
No. Rebalances still happen when membership or subscriptions change. It only makes each one cheaper by pausing fewer partitions. Static membership is what avoids some rebalances.
Q3Why does a moved partition need two rounds?
Because a partition must never have two owners at once. The first round takes it from the old owner, and the second gives it to the new one after the old owner has let go.
Q4Which Kafka version introduced cooperative rebalancing?
Kafka 2.4 added it for consumers. Kafka Connect received a similar feature in 2.3. Since Kafka 3.0 the default assignor list includes the cooperative sticky assignor as a second choice.
Q5Can I mix cooperative and eager consumers in one group?
Only through a shared assignor list, and the group will then use the eager protocol until every member supports only the cooperative one. This is the basis of the two-step upgrade.
Q6What happens to my consumer’s cache during a cooperative rebalance?
Entries for partitions you keep should stay. Entries for partitions being revoked should be removed in the revoke callback. Entries for new partitions must be built in the assign callback.
Q7Why did my group rebalance with no deployment?
Common causes are a member exceeding the poll interval, a heartbeat timeout after a long pause, an autoscaler event, a pattern subscription matching a new topic, or a coordinator failover. The broker’s coordinator log states the reason.
Q8Is static membership required for cooperative rebalancing?
No. They are independent features that work well together. Cooperative rebalancing softens rebalances, and static membership prevents some of them.
Q9Will I get fewer duplicates with cooperative rebalancing?
Usually yes, because fewer partitions change owner in each rebalance. But duplicates are still possible for the moved partitions, so handlers should be idempotent.
Q10Does the new server-side protocol replace all this?
It builds the incremental idea into the protocol itself and removes the group-wide barrier, so it is expected to become the long-term direction. The classic protocol with cooperative sticky remains supported and widely used.
Q11How many consumers should a group have?
No more than the number of partitions, since extra members would sit idle. Choose the count from throughput needs and keep some headroom, so that pauses are recovered quickly.
Q12Should I still worry about the poll interval?
Yes. Cooperative rebalancing limits the harm of a slow member, but a member that exceeds the poll interval is still removed, which causes its partitions to move.

SSummary and Key Takeaways

Move only what needs to move, and never stop what does not need to stop.

A rebalance is Kafka’s way of sharing partitions again when a group changes. The old, eager way makes everyone revoke everything and wait, even when most partitions would end up exactly where they were. Sticky assignment chooses the plan that changes the least. Cooperative rebalancing carries out that plan in stages, taking a moved partition from its old owner first and giving it to the new one in a second round. Combined, they turn a group-wide outage into a small, local handover. Add static membership, careful listeners and sensible timeouts, and most deployments stop being events at all.

Key Takeaways

  • Eager rebalancing stops the whole group. Every member revokes every partition and waits, even for partitions that will not move.
  • Sticky means minimal movement. The assignor balances the counts and keeps as many existing owners as it can.
  • Cooperative means incremental. Members keep their partitions, and only those that must move are revoked, in a first round, and reassigned, in a second.
  • A partition never has two owners at once, which is why a moved partition waits for two rounds while everything else keeps running.
  • Your listener must handle differences, not full sets. Revoke and assign callbacks carry only what changed, and may be empty.
  • Upgrade in two rolling restarts: list both assignors first, then remove the old one. Update listener code before you begin.
  • Pair it with static membership, bounded batches and idempotent handlers, and watch rebalance counts and durations on your dashboards.