How To

Kafka Consumer Lag Explained: 6 Signals That Actually Matter

Raw consumer lag is the most-watched and most-misread Kafka metric. Six relational signals (lag shape, rebalance storms, partition skew, URPs, lag age against retention) tell you whether consumers will catch up or lose data.

10 min read
Cover Image for Kafka Consumer Lag Explained: 6 Signals That Actually Matter

Kafka Consumer Lag Explained: 6 Signals That Actually Matter

There's a lag graph somewhere in your monitoring stack that spikes every time you deploy. It always comes back down, so at some point the team muted the alert. Then one Tuesday the spike didn't come back down, and nobody noticed for four hours, because nobody believed the lag alert anymore.

That's the standard failure mode of Kafka monitoring. Consumer lag is the most-watched Kafka metric and also the most misread. Raw lag is a number without context. A lag of 2 million records can be perfectly healthy on a topic doing 50K records per second, and a lag of 500 can be a five-alarm incident on a topic that feeds fraud detection. What matters isn't the number. It's the shape, the cause, and the handful of surrounding signals that tell you whether your consumers are falling behind for a reason that fixes itself or a reason that ends in data loss.

This guide walks through the six signals worth watching on any Kafka deployment, self-managed or Confluent Cloud, with one concrete detection step for each and a sober look at what each one costs when ignored.

This is part one of a three-part series. Part two is a hands-on lag triage guide using only Kafka's native tools; part three covers automating Kafka operations with AI agents.

01Consumer

Lag, defined

Log end offset minus committed offset, per partition. A count, not a duration.

02Consumer

Lag shape

Bursty, steady growth, or a plateau that never drains. Each needs a different response.

03Group

Rebalance storms

Evictions trigger rebalances that trigger more evictions. Lag climbs while everyone looks busy.

04Topic

Partition skew

One hot key pins one partition to one consumer. More consumers don’t help.

05Broker

Under-replicated

Followers falling behind. Often the real cause of consumer-side symptoms.

06Retention

Lag age

When the oldest unread record nears retention, lag turns into silent data loss.

The six signals this guide covers. The last one is the only failure you can't recover from.

Why a lag threshold can't be right

Most teams start with one alert: lag above N. It's wrong in both directions, for structural reasons rather than a bad choice of N.

  • Lag is a count, and the cost is time. 100,000 records is two seconds on one topic and two days on another. A single threshold can't mean the same thing across topics with different throughput.
  • Healthy topics are bursty. Kafka exists to absorb bursts. A threshold low enough to catch slow degradation fires on every batch job, so it gets muted.
  • Group totals hide partitions. A group-level lag of 100K looks the same whether it's spread across 50 partitions or sitting on one. Only one of those is a hot key.
  • Thresholds go stale. Traffic grows, the number set at launch stops meaning anything, and nobody owns retuning it.

The signals below avoid all four problems by being relational: lag against produce rate, lag age against retention, one partition against its siblings, rebalances against deploys.


1. What consumer lag actually measures

The signal. For each partition, lag is the log end offset (the offset of the newest record the broker has) minus the consumer group's committed offset. That's it. It's a count of records the group hasn't yet confirmed processing. It isn't a duration, it isn't a rate, and it isn't necessarily a problem.

Two subtleties bite people. First, lag is per partition. A "group lag" of 100K spread evenly across 50 partitions is a very different situation from 100K sitting on one partition. Second, lag is measured against committed offsets, so a consumer that processes records but commits infrequently (large auto.commit.interval.ms, or manual batched commits) shows phantom lag that vanishes on every commit.

How to check it. The canonical view comes from the CLI that ships with Kafka:

kafka-consumer-groups.sh --bootstrap-server broker:9092 \
  --describe --group payments-consumer

Read the CURRENT-OFFSET, LOG-END-OFFSET and LAG columns per partition. The Kafka documentation covers the full output format.

Why it matters. Every downstream consequence in this article (stale dashboards, missed SLAs, lost messages) starts as lag. But it only becomes useful once you know which of the following five patterns you're looking at.

2. The three shapes of lag: steady growth, bursts, and the plateau that never drains

The signal. Take any lag graph over 24 hours and it'll match one of three shapes:

01Shape 1

Bursty

Spikes with a producer surge and drains after. Kafka working as designed. Alert on drain time, not the spike.

02Shape 2

Steady growth

A straight line up. Consume rate is below produce rate, and waiting won’t fix it.

03Shape 3

Never drains

A plateau or sawtooth. Consumers at exactly 100% of capacity, one deploy away from shape 2.

Take any 24-hour lag graph and it matches one of these. The shape decides the response.
  • Bursty lag spikes when producers surge (a batch job, a traffic peak, a replay) and drains once the surge passes. This is Kafka working as designed: the log absorbed a burst your consumers couldn't process in real time. Alerting on the spike itself is how teams end up muting alerts.
  • Steady-growth lag climbs in a straight line. Your consume rate is simply below your produce rate. No amount of waiting fixes this. It ends when you add consumer capacity or when retention starts deleting unread data (signal 6).
  • Lag that never drains sits at a plateau, or sawtooths around one. Consumers keep pace with new traffic but never catch up on the backlog. It's often a sign they're running at exactly 100% of capacity, one deploy or one rebalance away from steady growth.

How to check it. Run the --describe command above twice, a few minutes apart, and compare. If lag grew and the LOG-END-OFFSET delta is much larger than the CURRENT-OFFSET delta, you're producing faster than you consume. A quick loop gives you the trend without any monitoring stack:

for i in 1 2 3; do
  kafka-consumer-groups.sh --bootstrap-server broker:9092 \
    --describe --group payments-consumer \
    | awk 'NR>1 {sum+=$6} END {print strftime("%H:%M:%S"), "total lag:", sum}'
  sleep 60
done

(strftime is a GNU awk function; on macOS, install gawk or drop the timestamp.) A time-series view (Prometheus plus an exporter, or Confluent Cloud's built-in charts) makes the shape obvious at a glance. Part two of this series covers building that view.

Why it matters. The shape dictates the response. Bursty lag needs a better alert (on drain time or on lag age, not absolute lag). Steady growth needs capacity today. A plateau needs capacity before the next traffic increase, not after.

3. Consumer group rebalancing storms

The signal. Consumer group rebalancing is how Kafka reassigns partitions when members join or leave. Each rebalance briefly pauses consumption. That's fine on deploys and corrosive when it happens continuously. A rebalance storm goes like this: a consumer takes longer than max.poll.interval.ms (default 5 minutes) to process a batch, the group coordinator assumes it died and evicts it, a rebalance fires, the reassignment makes other consumers reprocess and slow down, they miss their poll deadlines, and the group enters a loop where it spends more time rebalancing than consuming. Lag climbs while every individual consumer looks "busy."

The same loop triggers from the session side. If a consumer fails to heartbeat within session.timeout.ms (default 45 seconds since Kafka 3.0), usually because of a GC pause or a network blip, it's evicted just the same.

How to check it. The fastest tell is membership churn:

kafka-consumer-groups.sh --bootstrap-server broker:9092 \
  --describe --group payments-consumer --state

A state of PreparingRebalance or CompletingRebalance on repeated runs means the group can't stabilize. Consumer logs showing repeated Attempt to heartbeat failed since group is rebalancing confirm it. The consumer configuration docs define the three timeout settings involved; tuning them is a part-two topic.

Why it matters. Rebalance storms are the most common cause of "lag spikes on every deploy." They also cause duplicate processing, since evicted consumers rarely commit their in-flight work. If your pipeline isn't idempotent, every storm is quietly double-counting something.

4. Partition skew and hot partitions

The signal. Kafka parallelism is per partition: one partition is consumed by at most one consumer in a group. If your producers key messages by something unevenly distributed (customer ID where one customer is 40% of traffic, say), one partition gets a disproportionate share, and the single consumer stuck with it becomes the bottleneck. Group-level lag looks moderate. Partition-level lag tells the truth.

How to check it. Same --describe output, different reading: scan the LAG column for one or two partitions carrying most of the total while the rest sit near zero. Then check whether the skew starts at the producer by comparing LOG-END-OFFSET growth across partitions. A hot partition's end offset advances visibly faster than its siblings'.

Why it matters. Hot partitions cap your throughput at the speed of one consumer thread, no matter how many consumers you add. Extra members beyond the partition count just sit idle. The fix is a keying-strategy or partition-count change, which is a data-model conversation, not a scaling knob. The earlier you spot skew, the cheaper that conversation is.

5. Under-replicated partitions: the broker-side red flag

The signal. Everything so far is consumer-side. Under-replicated partitions (URPs) are the broker-side counterpart: a partition whose in-sync replica set has shrunk below its replication factor, because a follower broker is down, restarting, or too slow to keep up. URPs often cause consumer symptoms. Fetches slow down, and if replicas drop below min.insync.replicas, producers using acks=all start failing outright, which shows up downstream as mysterious gaps and retries.

How to check it.

kafka-topics.sh --bootstrap-server broker:9092 \
  --describe --under-replicated-partitions

Empty output is the only good output. Anything listed deserves attention before you touch a single consumer setting. (On Confluent Cloud, replication is managed for you and this signal is handled by Confluent, one genuine advantage of the managed service.)

Why it matters. A cluster running with URPs is one broker failure away from data loss on those partitions. It's also the signal that tells you a lag incident isn't your consumers' fault at all, a distinction that saves hours of tuning the wrong layer.

6. Retention pressure: when lag turns into data loss

The signal. Kafka topics delete data on a schedule: retention.ms, commonly 7 days, sometimes far less for high-volume topics. Lag is measured in records, but the clock that matters is lag age: how old is the oldest unconsumed record? When lag age approaches retention, the broker deletes records your consumers never read. The consumer then hits an OffsetOutOfRangeException and falls back to auto.offset.reset. With latest it silently skips the lost data; with earliest it triggers a reprocessing avalanche. Either way, the loss itself is silent. No error names the records that vanished.

How to check it. Watch the consumer's own view of its worst partition via JMX: the records-lag-max metric under kafka.consumer:type=consumer-fetch-manager-metrics (metrics reference). Divide by your per-partition consume rate to estimate hours to catch up, and compare against the topic's retention:

kafka-configs.sh --bootstrap-server broker:9092 \
  --describe --entity-type topics --entity-name payments.events --all \
  | grep retention.ms

On Confluent Cloud, the console shows per-group lag under your cluster's Clients → Consumer lag view, and the Metrics API exposes consumer_lag_offsets for alerting.

Why it matters. This is the one signal with an unrecoverable failure state. Stale dashboards annoy people; missed SLAs cost credits; records deleted past retention are simply gone. Any consumer group whose realistic catch-up time exceeds half your retention window should be treated as an incident, even if today's lag number looks tolerable.


Raw lag alerts versus the six signals

QuestionRaw lag thresholdRelational signal
Is this burst going to drain?Fires either wayLag trend against produce rate
Are we about to lose data?Can't tellLag age against retention.ms
Will more consumers help?Can't tellPartition lag against the group average
Is the consumer the problem at all?Assumes yesURPs and broker health first
Why does lag spike on deploys?Fires, gets mutedRebalance frequency against deploy times

Every row on the right needs two numbers compared over time, which is exactly what a static threshold can't do.

What these six signals have in common

None of them is an absolute-lag threshold. Raw lag alerts produce noise on bursty topics and silence on slowly degrading ones, which is how teams end up ignoring the graph. The signals that predict real incidents compare: lag trend against produce rate, lag age against retention, partition-level lag against the group average, rebalance frequency against deploy frequency.

The catch is that checking them that way takes ongoing effort. Each command above is a snapshot. The shapes and trends only emerge from repeated measurement. Part two of this series builds that measurement loop with nothing but Kafka's native tools: kafka-consumer-groups.sh, JMX metrics, the timeout triangle, and their Confluent Cloud equivalents.

Watching the signals continuously

Everything in this article can be watched around the clock. CloudThinker connects to Kafka or Confluent Cloud with read-only access and continuously tracks consumer lag, rebalance frequency, partition skew and under-replicated partitions. When a trend breaks, it investigates the cause instead of paging you with a raw number. Part three shows how that works.

Related reading

Try CloudThinker free with 100 premium credits and no card required, or continue with the DIY triage guide.

kafkaobservabilitystreamingconsumerlagkafkamonitoringconfluentcloud