Search... ⌘K
Write Log In Sign Up

Join DevSolved

Share war stories & investigate outages.

Sign Up for Free Log In to Account
ESC
CRITICAL Resolved

Kafka Consumer Lag Death Spiral: The Rebalance Storm

Avatar Devsolved Nov 5, 2023 — 09:00 UTC MTTR: 18 hours 2 min read 180000 views
Executive Summary

A slight degradation in database write latency caused our Kafka consumers to miss their heartbeat intervals. This triggered an endless loop of consumer group rebalancing, entirely halting a pipeline processing 50,000 messages per second.

Data Pipeline Impact: 400 million analytics events were delayed by 8 hours, causing massive reporting discrepancies for enterprise clients.

The Symptom

Symptom

Our primary analytics consumer group started exhibiting massive lag. The Datadog dashboard showed consumer lag spiking from near-zero to over 50 million messages. Strangely, the CPU on the consumer microservices dropped to near zero, indicating they were doing absolutely no work. Kafka broker logs were flooded with `Preparing to rebalance group` and `Member has left group`.

Timeline

  • 1
    18:00 update

  • 2
    18:05 update

  • 3
    18:15 update

  • 4
    19:00 update

  • 5
    22:00 update

Root Cause Analysis

Root Cause

Kafka relies on consumer heartbeats to know if a consumer is alive. However, Kafka also enforces a `max.poll.interval.ms` (default 5 minutes). This means a consumer *must* finish processing its batch of messages and call `.poll()` again within 5 minutes. Because our database write latency spiked, processing a single batch of 500 messages suddenly took 6 minutes. The Kafka broker assumed the consumer was dead (livelock), kicked it out of the group, and triggered a "Rebalance" to reassign the partitions to other consumers. During a rebalance, ALL consumers in the group pause processing. Once the rebalance finished, a new consumer picked up the exact same heavy batch, took 6 minutes, got kicked out, and triggered ANOTHER rebalance. This loop paralyzed the entire 50-node cluster.

The crux of the issue was a fundamental misunderstanding of how Kafka handles backpressure. We thought that as long as the background heartbeat thread was ticking, Kafka would be happy. We didn't realize the main processing thread had a strict deadline.

Code javascript
// The fatal configuration (using defaults)
const consumer = kafka.consumer({ groupId: 'analytics-group' })
await consumer.run({
  eachBatch: async ({ batch }) => {
    // If this takes > 5 minutes (300000ms), Kafka kills the consumer
    await insertIntoDatabase(batch.messages);
  }
})

5 Whys Root Cause Drill-Down

1
Why #1
2
Why #2
3
Why #3
4
Why #4
5
Why #5

Resolution

Resolution

We implemented two critical fixes. First, we drastically reduced `max.poll.records` from 500 to 50. This ensures a batch processes much faster, easily beating the 5-minute deadline even if the database is slow. Second, we explicitly increased `max.poll.interval.ms` to 10 minutes to provide a buffer for temporary downstream degradation.
Code javascript
// The fix: Tune batch sizes and timeouts
const consumer = kafka.consumer({
  groupId: 'analytics-group',
  maxWaitTimeInMs: 100,
})
await consumer.run({
  eachBatchAutoResolve: true,
  partitionsConsumedConcurrently: 1,
  // Keep batch small so it processes fast
})

Preventive Action Items

critical Audit `max.poll.records` on all Kafka consumer microservices @Data Team
high Create specific alerts for Kafka Rebalance Rate metrics @DevOps
|

Discussions 0

Most recent