← Blog

Kafka · Consumer Lag · Backpressure

What Does Rising Kafka Consumer Lag Actually Mean—and Why Can Adding Consumers Make It Worse?

Kafka consumer lag is a symptom, not a diagnosis. This article explains how to reason about backlog through arrival rate, processing rate, event age, partition skew, downstream bottlenecks, poll timing, poison records, rebalances, and autoscaling—and why adding consumers can sometimes increase lag instead of reducing it.

Aashaya Aryal

January 8, 2025 · 22 min read

Updated June 11, 2025

Close-up of an analytics dashboard displaying metric cards, line charts, a CTR percentage, and a quality score.

A Kafka alert fires:

TEXT
consumer lag = 1,000,000

The instinctive response is simple:

Add consumers.

Sometimes that is exactly the right move. Sometimes it changes nothing. And sometimes it makes the incident worse by increasing downstream pressure or repeatedly forcing the consumer group through rebalances.

That is because consumer lag is not a diagnosis. It is a symptom.

The same rising backlog can come from very different conditions:

TEXT
traffic increased

a database became slower

one partition became hot

consumers are repeatedly rebalancing

processing one record now takes much longer

a poison event is blocking one partition

Every one of those scenarios can produce:

TEXT
lag ↑

but the correct response is different in each case.

Kafka's consumer model makes this distinction especially important. Partitions are distributed among members of a consumer group, and one partition is owned by one member of that group at a time. Adding consumers therefore increases useful parallelism only while there are useful partitions available to assign—and only while the rest of the processing path has spare capacity.

So instead of beginning with:

“How high is consumer lag?”

I find this question much more useful:

“Why is work entering this consumer group faster than useful work is leaving it?”

Once lag is treated as a throughput and queueing problem rather than one scary dashboard number, the incident becomes much easier to reason about.

Consumer Lag Measures Distance, Not Cause

A Kafka partition is an ordered log.

Imagine records extending through offset 901, while the consumer group has committed only through 701:

TEXT
latest available offset = 901
consumer committed      = 701

Conceptually:

TEXT
lag ≈ 200 records
Diagram

offset 1

...

offset 701

Consumer position

702

...

901

Log end

Lag = unprocessed distance

Amazon MSK describes consumer lag in essentially this way: the difference between the latest available data and how far the consuming application has progressed. MSK exposes several related measurements, including total offset lag, maximum offset lag, and estimated time lag.

That number tells us how far behind the group is.

It does not tell us why the distance exists.

A million records of lag could be a harmless burst already draining rapidly, a permanently underprovisioned consumer group, one pathological partition, or a downstream system that has suddenly become 20 times slower.

The number alone cannot distinguish them.

Lag Is a Rate Problem

The simplest useful model uses two rates:

TEXT
λ = arrival rate
μ = processing rate

If producers append:

TEXT
10,000 records/sec

while consumers process:

TEXT
12,000 records/sec

then:

TEXT
μ > λ

and existing lag shrinks.

If:

TEXT
λ = μ

the backlog remains roughly stable.

If:

TEXT
λ > μ

the backlog grows.

The basic relationship is:

TEXT
backlog growth rate
≈
arrival rate - processing rate

That tells us much more than the absolute lag value.

The same lag can describe three different systems

Suppose three consumer groups all report:

TEXT
lag = 1,000,000

System A receives:

TEXT
arrival rate     = 8,000/sec
processing rate  = 12,000/sec

Its recovery capacity is:

TEXT
4,000/sec

so approximate drain time is:

TEXT
1,000,000 / 4,000
= 250 seconds

The backlog is large, but the system is recovering.

System B receives and processes:

TEXT
arrival rate     = 10,000/sec
processing rate  = 10,000/sec

The million-record backlog never becomes larger, but it never becomes smaller either. The system can sustain current traffic but has no recovery capacity.

System C receives:

TEXT
12,000/sec

and processes:

TEXT
8,000/sec

Lag grows by:

TEXT
4,000/sec

After another five minutes, approximately:

TEXT
1.2 million

additional records will have accumulated.

Same lag value.

Three completely different operational states.

Monitor direction as well as magnitude

I would rather see:

TEXT
lag:        1,000,000
lag delta:  -4,000/sec

than only:

TEXT
lag: 1,000,000

The first tells me:

TEXT
behind, but recovering

Likewise:

TEXT
lag:        20,000
lag delta:  +5,000/sec

may deserve more attention than a million-record backlog that is draining rapidly.

The absolute number is smaller.

The trajectory is worse.

Age Can Matter More Than Record Count

Imagine a consumer processing financial events.

A backlog of:

TEXT
500,000 records

sounds alarming.

But suppose the producer generates:

TEXT
1,000,000 records/sec

Those records could represent less than one second of work.

Now consider another topic with only:

TEXT
5,000 records

of lag, but the oldest event has been waiting:

TEXT
45 minutes

For many applications, the second system is in much worse shape.

This is why estimated time lag is so useful. Record count tells us how many offsets separate the consumer from the head of the log. Event age tells us how stale the business processing has become.

For user-visible systems, I want both:

TEXT
offset lag
+
oldest event age / estimated time lag

because the actual SLO often looks more like:

Process this event within 30 seconds.

than:

Keep the offset difference below 50,000.

A queue metric becomes much more meaningful when expressed in the same unit the business cares about: time.

Adding Consumers Only Helps When Parallel Work Exists

Suppose a topic has:

TEXT
12 partitions

and a consumer group currently has:

TEXT
4 consumers

Kafka might distribute partitions roughly like this:

TEXT
Consumer 1 → 3 partitions
Consumer 2 → 3 partitions
Consumer 3 → 3 partitions
Consumer 4 → 3 partitions

Scaling to:

TEXT
12 consumers

can produce roughly:

TEXT
1 partition per consumer

which is excellent if each partition has enough work.

Scale again to:

TEXT
20 consumers

and several members may have nothing to consume.

Diagram

Partition 1

Partition 2

Partition 3

Partition 4

Consumer 1

Consumer 2

Consumer 3

Consumer 4

Consumer 5

Idle

Consumer 6

Idle

A useful rule is:

TEXT
useful consumer count
≤
available partitions

Adding replicas past that point can increase:

TEXT
cost
connections
deployment complexity
group membership churn

without increasing message-processing throughput.

This is why:

TEXT
lag high
→ replicas++

is not a complete scaling strategy.

Partition count places an upper bound on how much group-level parallelism Kafka can expose.

Partition Skew Can Defeat Horizontal Scaling

Partition count alone does not guarantee that work is evenly distributed.

Suppose a topic has:

TEXT
12 partitions
12 consumers

which sounds ideal.

But the traffic distribution looks like:

TEXT
Partition 0:    100 records/sec
Partition 1:    110 records/sec
Partition 2:     95 records/sec
...
Partition 11: 8,000 records/sec

Now one consumer receives dramatically more work than the rest:

Diagram

Partition 11

8,000 msg/s

Partition 1

100 msg/s

Partition 2

110 msg/s

Consumer A

HOT

Consumer B

Consumer C

The group can report large total lag even though most consumer instances are almost idle.

Adding more consumers does nothing.

Partition 11 still belongs to one consumer.

That is why I want:

TEXT
lag per partition

not merely:

TEXT
total consumer-group lag

Hot partitions often begin with the key

Suppose events use:

TEXT
customerId

as the partition key.

Most customers produce:

TEXT
100 events/hour

while one customer produces:

TEXT
5,000,000/hour

That customer's events consistently map into one partition.

Kafka is doing exactly what the key asked it to do.

The skew comes from the application-level distribution.

Possible responses include:

TEXT
choose a better distributed key

split extremely hot identities

reduce the scope across which strict ordering is required

but those choices affect semantics.

If every event for one customer must remain strictly ordered, spreading that customer's stream across multiple partitions may violate the guarantee the partition key was chosen to provide.

Kafka partitioning therefore links two things that are easy to discuss separately:

TEXT
throughput
and
ordering

Changing one can change the other.

The Bottleneck May Be Downstream

Now consider a consumer whose processing path is:

TEXT
Kafka
↓
deserialize
↓
validate
↓
PostgreSQL INSERT

Lag starts increasing.

Consumer CPU is:

TEXT
27%

Memory looks fine.

Pods are healthy.

The tempting response is:

TEXT
10 consumers
→ 30 consumers

But all 30 now write to PostgreSQL.

Database connections increase. CPU increases. Lock contention rises. Query latency gets worse, and each Kafka record takes longer to finish.

Diagram

Lag Rising

Add Consumers

More Concurrent DB Writes

Database Saturates

Each Record Takes Longer

Lag Rises Faster

The consumer layer was never the limiting resource.

The database was.

This is why scaling Kafka consumers has to consider the entire processing path, not only Kafka.

Downstream latency can appear as Kafka lag

Suppose normal per-record processing takes:

TEXT
20 ms

Then an HTTP dependency slows to:

TEXT
500 ms

Kafka is healthy.

The consumer process is healthy.

Nothing crashes.

But each instance now completes only a fraction of its old throughput.

Lag rises.

The correct action may be:

TEXT
fix or protect the downstream service

rather than:

TEXT
add more consumers

If new consumers simply create more simultaneous calls to a degraded dependency, scaling can amplify the incident.

Concurrency improves throughput only while useful capacity still exists somewhere downstream.

Once that capacity is saturated, more parallelism creates contention.

Poll Timing Can Turn Slow Processing Into Rebalances

Kafka consumers repeatedly call:

JAVA
consumer.poll(...)

and are expected to continue polling within their configured membership timing.

max.poll.interval.ms defines how long a consumer participating in group management may go between poll() calls before it can be considered failed and its partitions become eligible for reassignment.

Suppose:

TEXT
max.poll.records = 500

and each record now requires:

TEXT
800 ms

of serial processing.

Worst-case batch duration becomes:

TEXT
500 × 0.8 sec
=
400 seconds

If:

TEXT
max.poll.interval.ms = 300 seconds

the consumer can be actively doing useful work while Kafka sees something closer to:

TEXT
not polling

The group can then reassign its partitions.

Processing pauses or moves.

Lag rises further.

Slow and dead can look similar to group coordination

There is an important difference between:

TEXT
consumer crashed

and:

TEXT
consumer alive but taking too long
between poll calls

but from group membership's point of view, both can lead to reassignment depending on configuration.

That is why these values have to be considered together:

TEXT
max.poll.records
max.poll.interval.ms
actual record processing time

Possible fixes include reducing batch size, optimizing the slow operation, or restructuring processing carefully so polling and work execution are not coupled in a way that violates group timing.

Blindly increasing the timeout forever has a downside too: a consumer that is genuinely stuck now takes longer to detect.

Every timeout encodes a failure policy.

Scaling Can Create Rebalance Churn

Consumer groups need to redistribute partitions when membership changes.

That happens when:

TEXT
a consumer joins
a consumer leaves
a consumer crashes
a deployment replaces Pods
an autoscaler changes replica count

With the classic consumer-group behavior, those membership changes can temporarily interrupt useful processing while assignments are coordinated.

AWS has explicitly warned that scaling Kafka consumers can create rebalances that pause processing, allowing lag to increase during the very action intended to reduce it.

That creates an uncomfortable control loop.

Lag-based autoscaling can react to its own disruption

Suppose KEDA observes:

TEXT
lag = 200k
consumers = 4

and scales the group by four instances.

New consumers join.

The group rebalances.

Useful processing temporarily slows.

Lag increases to:

TEXT
300k

The autoscaler sees:

TEXT
lag got worse

and adds more consumers.

Another membership change occurs.

Another rebalance follows.

Diagram

Lag High

Autoscaler Adds Consumers

Consumer Group Rebalances

Processing Temporarily Slows

Lag Rises

Autoscaler Adds More

AWS has documented this exact concern in Kafka/KEDA architectures, which is why stabilization windows and additional signals such as inbound traffic rate matter.

Scaling is not instantaneous capacity.

It has a transition cost.

Kafka's newer consumer protocol improves the transition

Kafka's newer consumer rebalance protocol, generally available beginning with Kafka 4.0 through KIP-848, moves more assignment coordination to the broker and supports more incremental behavior than the classic all-group synchronization model.

That can reduce disruption during membership changes.

It does not make scaling free.

A new consumer still needs:

TEXT
startup
partition assignment
cache warmup
database connections
JVM warmup
downstream capacity

and the group still cannot scale past its useful partition-level or dependency-level parallelism.

Deployment churn can become coordination churn

A Kubernetes rollout of 20 Kafka consumer Pods is not merely replacing containers.

It is repeatedly changing distributed group membership:

TEXT
Pod leaves
→ rebalance

replacement joins
→ rebalance

next Pod leaves
→ rebalance

Depending on protocol and assignment strategy, a poorly behaved rollout can spend a surprising amount of time moving partition ownership instead of processing records.

Clean consumer shutdown, stable membership where appropriate, and sticky or cooperative assignment behavior can reduce unnecessary movement.

A consumer deployment changes distributed coordination state.

Not just Pods.

Poison Records Create Localized Backlogs

Suppose partition 4 contains:

TEXT
offset 1001  processed
offset 1002  processed
offset 1003  malformed
offset 1004  valid
offset 1005  valid
...

The consumer reaches offset 1003.

Processing fails.

The retry policy is:

TEXT
retry forever

The partition stops progressing.

Diagram

blocks progress

1001

processed

1002

processed

1003

fails repeatedly

1004

1005

1006

Kafka is alive.

The consumer is alive.

CPU may even be low.

Lag for that partition grows indefinitely.

Adding consumers cannot solve it because the bad record remains inside one ordered partition.

The system needs an explicit poison-record policy.

Dead-letter queues trade ordering for progress

One policy is:

TEXT
try record N times
↓
still failing
↓
send to dead-letter topic
↓
continue processing

That restores progress.

It also means offset 1004 may be processed while 1003 remains unresolved somewhere else.

For independent events, that can be exactly right.

For a strict sequence such as:

TEXT
balanceChanged v7
balanceChanged v8

skipping version 7 and applying version 8 may be invalid.

A dead-letter policy is therefore not merely an operational Kafka setting.

It is a business-ordering decision.

Retries can consume the throughput needed for recovery

One failing record may be attempted:

TEXT
attempt 1
attempt 2
attempt 3
attempt 4
attempt 5

while new records continue arriving.

If the dependency is failing globally, every record can enter the same retry behavior until:

TEXT
useful processing rate
→ almost zero

while:

TEXT
arrival rate
→ unchanged

A retry strategy that looks safe for one message can become a backlog generator when applied to an entire stream.

Bounded attempts, backoff, and explicit failure handling matter in consumers for the same reason they matter in synchronous distributed systems.

Large Backlogs Change the Recovery Problem

Kafka is good at storing durable backlog.

That does not mean every backlog is operationally harmless.

Suppose:

TEXT
lag = 2,000,000,000 records

and after serving current traffic, the consumer group has only:

TEXT
5,000 records/sec

of spare recovery capacity.

Estimated drain time becomes:

TEXT
2,000,000,000 / 5,000
=
400,000 seconds

or more than:

TEXT
4.6 days

The consumer is technically recovering.

The business may not be able to wait four days.

This is where the distinction between:

TEXT
stable

and:

TEXT
recoverable within the required deadline

becomes important.

A queue buys time.

It does not create processing capacity.

If arrival continues exceeding completion for long enough, eventually the backlog needs a change in processing capacity, incoming demand, prioritization, or work semantics.

Not Every Event Is Worth Processing Forever

Consider:

TEXT
device.temperature.updated

If a real-time alerting pipeline is eight hours behind, does every old reading still deserve expensive enrichment?

Maybe yes for historical analytics.

Maybe no for live anomaly detection.

Or consider:

TEXT
user.typing.started

from eight hours ago.

The event may now be meaningless.

Backlog recovery can therefore include policies such as:

TEXT
drop stale work

coalesce superseded events

prioritize newer work

separate critical and noncritical streams

depending on the application's semantics.

Durable storage does not imply infinite business value.

That distinction can be crucial during recovery from a very large queue.

Lag Needs Business Priority

Suppose one consumer pool is responsible for:

TEXT
payment.completed
marketing.analytics
email.opened
recommendation.refresh

During overload, analytics work may consume the same CPU, database connections, or external-service quota required to process payments.

A stronger design may isolate workloads:

Diagram

Kafka

Critical Consumer Pool

Background Consumer Pool

Payments

Analytics / Refresh

Now:

TEXT
critical lag

and:

TEXT
background lag

can have different alert thresholds, scaling behavior, and recovery priorities.

This is capacity isolation applied to event processing.

A single total consumer lag number often obscures exactly the information operations needs most: which business work is late?

A Better Kafka Consumer Dashboard

Suppose total lag is:

TEXT
1,000,000

There are at least two very different ways it might be distributed:

TEXT
100 partitions × 10,000 each

or:

TEXT
99 partitions × 0
1 partition   × 1,000,000

The first can indicate broadly insufficient capacity.

The second suggests something local:

TEXT
hot partition
poison record
slow record type
bad key distribution

I would want a partition-level view resembling:

PartitionLagInput RateProcess RateOldest Age
0200500/s510/s0.4 s
1170480/s500/s0.3 s
2850,0007,000/s2,000/s14 min
3110450/s470/s0.2 s

Partition 2 is the incident.

The total is mostly a summary.

Consumer lag needs context from the entire path

A useful dashboard should connect Kafka state to consumer behavior and downstream capacity.

TEXT
Kafka
-----
per-partition lag
oldest message age
arrival rate
group state
rebalance rate

Consumers
---------
records/sec
batch processing time
poll interval
errors
retries
CPU
memory

Downstream
----------
database latency
connection pool utilization
HTTP dependency latency
429s
5xx
timeouts

Autoscaling
-----------
desired consumers
actual consumers
recent scaling events
startup duration

The dashboard should make it possible to answer:

Where did useful throughput disappear?

not merely:

How far behind are we?

Missing lag data can also be a signal

Amazon MSK notes an important observability detail: consumer-lag metrics are emitted when a consumer group is in appropriate stable or empty states.

During instability such as rebalancing, normal lag metrics may stop appearing.

That means:

TEXT
no lag datapoint

must not automatically be interpreted as:

TEXT
lag = 0

If the group is repeatedly unstable, missing lag data can be part of the incident itself.

Monitor:

TEXT
group state
rebalance frequency
consumer membership

alongside the lag series.

Autoscaling Should Measure Whether It Actually Helped

CPU-based autoscaling can miss queue pressure entirely.

A consumer blocked on:

TEXT
database connections
HTTP dependency
disk
rate-limited provider

may show:

TEXT
CPU = 25%

while lag grows rapidly.

That is why queue-driven consumers often benefit from work-oriented signals such as lag.

But lag alone is not enough either.

The strongest scaling decisions consider:

TEXT
backlog
+
arrival rate
+
processing rate
+
partition availability
+
downstream capacity

and ideally ask whether previous scaling actually improved useful throughput.

Suppose:

TEXT
arrival    = 10k/s
processing = 8k/s
lag        = 1m

After adding consumers:

TEXT
arrival    = 10k/s
processing = 14k/s

Excellent.

The group now has:

TEXT
4k/s

of recovery capacity.

Scaling again immediately may not be necessary.

Now consider:

TEXT
arrival    = 10k/s
processing = 8.5k/s

after the same scale-out.

The fleet became much larger and throughput barely changed.

That is evidence that consumer compute was probably not the main bottleneck.

A useful autoscaling control loop should ask:

Did the last scaling action increase completed work enough to justify the next one?

Otherwise, the scaler can keep adding instances to a bottleneck it does not control.

Estimated Drain Time Makes Lag Actionable

When:

TEXT
processing_rate > arrival_rate

we can estimate:

TEXT
drain time
≈
lag / (processing_rate - arrival_rate)

Suppose:

TEXT
lag             = 5,000,000
arrival rate    = 20,000/sec
processing rate = 25,000/sec

Recovery capacity is:

TEXT
5,000/sec

so estimated drain time is:

TEXT
~1,000 seconds
≈ 16.7 minutes

That is operationally useful.

An operator can ask:

Is 17 minutes acceptable?

If yes:

TEXT
let the system recover

If no:

TEXT
increase safe processing capacity
or reduce incoming work

That is far more useful than:

TEXT
lag alarm = RED

Sometimes drain time does not exist

If:

TEXT
processing_rate < arrival_rate

then:

TEXT
processing_rate - arrival_rate < 0

There is no finite drain time.

The backlog is not recovering.

It is expanding.

A dashboard should make that state obvious:

TEXT
Lag:                   5.0M
Oldest event:          18m
Arrival rate:          20k/s
Processing rate:       17k/s
Backlog growth:        +3k/s
Estimated drain time:  ∞

That dashboard tells the truth immediately.

Rebalance rate belongs beside lag

If lag spikes whenever:

TEXT
consumer count changes

or:

TEXT
a deployment begins

the incident may be coordination-related.

A useful correlation is:

TEXT
lag ↑
rebalance rate ↑
consumer count oscillating

That tells a very different story from:

TEXT
lag ↑
rebalance rate stable
database latency ↑

One points toward group instability.

The other points downstream.

A Practical Incident Decision Tree

When consumer lag starts rising, I would reason through it approximately like this:

Diagram

No

Yes

Yes

No

Yes

No

Yes

Consumer Lag Rising

Most partitions affected?

One / few partitions

Most partitions

Consumers CPU-bound?

Increase safe compute

or optimize work

Downstream latency rising?

Fix or protect dependency

Rebalances frequent?

Stabilize group / autoscaling

Inspect hot key,

poison record,

partition skew

It is not exhaustive.

It does one important thing: it prevents this reflex from being the entire incident response:

TEXT
lag high
→ replicas++

The first question should be whether the lag is broad or localized.

Then whether useful work is constrained in the consumer itself, the downstream path, the group-coordination layer, or the partitioning model.

More Partitions Can Help, but They Are Not Emergency Magic

Suppose the topic has:

TEXT
12 partitions

and:

TEXT
12 consumers

All consumers are saturated, downstream systems still have spare capacity, and arrival rate remains above processing rate.

Now partition count may genuinely be the parallelism ceiling.

Increasing partitions can unlock additional consumer-level concurrency.

But partition count affects more than throughput:

TEXT
broker metadata
open files
replication
consumer assignments
ordering
key distribution
operational overhead

And adding partitions does not magically redistribute historical records as though Kafka were a generic shard balancer.

Future records may map differently depending on the key and partitioner, while old data remains where it already exists.

Partition count should therefore be capacity planning.

Not emergency magic.

Lag Is Not Automatically an Incident

Queues exist so producers and consumers do not need identical instantaneous throughput.

Imagine:

TEXT
traffic = 10× normal
duration = 30 seconds

Consumers fall behind.

Kafka absorbs the temporary mismatch.

Traffic returns to normal.

Consumers drain the backlog.

That is not necessarily a failure.

It can be the system working exactly as designed.

Paging immediately whenever:

TEXT
lag > 0

defeats much of the purpose of having a queue.

The more useful question is:

TEXT
Is lag outside the expected envelope,
and can the consumer group recover
within the business deadline?

Alert conditions can reflect that more directly.

For example:

TEXT
oldest event age > 5 minutes
AND
lag growth > 0

or:

TEXT
estimated drain time > 30 minutes

or:

TEXT
one partition contains > 70% of group lag

or:

TEXT
consumer-group rebalance rate is abnormally high

Those alerts describe failure modes.

A static threshold like:

TEXT
lag > 100,000

mostly describes volume.

The Invariants I Want

For Kafka consumers, I care about guarantees like:

TEXT
Every important consumer group must expose both
backlog size and backlog age.

Lag must be observable per partition, not only as
a group-level total.

Operators must know whether the backlog is growing,
stable, or draining.

The group must expose arrival rate and useful
processing rate separately.

Consumer scaling must not exceed useful partition
parallelism.

Adding consumers must not overload the dependency
those consumers call.

Consumer processing must reliably fit inside its
poll and membership timing constraints.

Autoscaling must not repeatedly destabilize the
group faster than new capacity becomes useful.

Scaling decisions should measure whether additional
consumers actually increased completed throughput.

A poison record must have an explicit failure policy
rather than silently blocking one partition forever.

Retry behavior must not be able to reduce useful
processing throughput to nearly zero indefinitely.

Critical and disposable workloads should not share
recovery capacity accidentally.

The system must estimate whether backlog can recover
inside the business deadline.

Missing lag telemetry during group instability must
not be mistaken for zero lag.

Partition count should be planned as a throughput
and ordering decision, not changed reflexively
during every lag incident.

Those are much stronger than:

TEXT
consumer lag alarm exists

because an alarm only tells us that the queue changed.

The invariants help explain what kind of change occurred and what the system is expected to do next.

Final Thought

Kafka consumer lag looks deceptively concrete:

TEXT
1,284,921

It feels like a number that should have one obvious interpretation.

It does not.

One million records behind can mean:

TEXT
healthy burst absorption

no spare recovery capacity

runaway backlog

one poisoned partition

one giant customer

database saturation

consumer rebalance churn

processing batches exceeding the poll interval

Those incidents can look similar on a single graph and require nearly opposite responses.

That is why adding consumers is not automatically the answer.

Consumers help only when Kafka can expose more useful parallel work and the rest of the processing path can absorb it. Beyond that point, additional consumers can become idle, generate extra database pressure, increase group churn, or do all three at once.

So when lag starts climbing, I would not begin with:

“How many Pods should we add?”

I would begin with four measurements:

TEXT
arrival rate

processing rate

oldest event age

lag distribution by partition

Then I would ask:

“Are we falling behind because we lack consumer capacity—or because something else is preventing that capacity from turning into completed work?”

That distinction is the difference between scaling a consumer group and actually fixing one.

References

  • Apache Kafka — Consumer Groups and Consumer Design. Describes how partitions are divided among members of a consumer group, why membership changes require assignment changes, and why useful consumer parallelism is ultimately bounded by topic partitioning.

  • Apache Kafka — Consumer Configuration. Defines settings including max.poll.interval.ms, max.poll.records, and partition-assignment behavior, including reassignment when a consumer does not continue polling within its configured interval.

  • Amazon MSK — Consumer Lag Metrics. Defines offset- and time-based lag metrics and notes that normal consumer-lag telemetry may not be emitted while a group is unstable during rebalancing.

  • AWS — Kafka Autoscaling with KEDA. Describes lag-driven consumer autoscaling and the feedback loop in which adding consumers can trigger rebalancing, temporarily reduce useful processing, increase lag, and cause additional scaling before the group stabilizes.

  • AWS — Kafka Consumer Client Guidance. Discusses clean consumer shutdown, static membership considerations, and sticky or cooperative assignment strategies for reducing unnecessary partition movement and disruptive rebalances.

  • Apache Kafka — KIP-848: The Next Generation of the Consumer Rebalance Protocol. Documents the newer broker-driven consumer rebalance protocol, generally available beginning with Kafka 4.0, designed to reduce disruption associated with classic group-wide rebalances.

  • Amazon Builders' Library — Queueing and Backlog Guidance. Discusses the broader principle that queues absorb temporary mismatches between arrival and processing rates, while persistent overload eventually requires more useful capacity, prioritization, reduced input, or different treatment of stale work.