Skip to content

fix: use stable Kafka warmup group ID and offset-aware catch-up (fixes #19)#21

Open
jishanahmed-shaikh wants to merge 1 commit into
11byte:mainfrom
jishanahmed-shaikh:fix/kafka-warmup-stable-offsets
Open

fix: use stable Kafka warmup group ID and offset-aware catch-up (fixes #19)#21
jishanahmed-shaikh wants to merge 1 commit into
11byte:mainfrom
jishanahmed-shaikh:fix/kafka-warmup-stable-offsets

Conversation

@jishanahmed-shaikh

@jishanahmed-shaikh jishanahmed-shaikh commented May 2, 2026

Copy link
Copy Markdown

Problem (Closes #19)

The warmupKafkaCache() function in
backend/src/utils/warmupKafka.ts used a time-based consumer group ID on every startup:

groupId: `warmup-consumer-${Date.now()}`

Because Kafka had no record of what this group had already processed, it always started from offset 0 and replayed every message ever produced.

Impact

  • Startup time grew linearly with total message history
  • ~100K messages → several minutes
  • ~1M messages → unusable startup time (timeouts)

Secondary Issue

The consumer stopped after an arbitrary 1-second delay:

await new Promise(r => setTimeout(r, 1000));

This caused:

  • ❌ Missed messages under load
  • ❌ Wasted time when idle

Root Cause

Two independent bugs:

1. Non-stable group ID

  • A new group ID on every restart
  • Kafka cannot commit offsets
  • Always replays from the beginning

2. Arbitrary sleep as stop condition

  • No guarantee all messages are consumed
  • Not tied to actual Kafka state

Fix

1. Stable Consumer Group ID

// Before
groupId: `warmup-consumer-${Date.now()}`

// After
groupId: 'ascent-warmup-stable'

Behavior

  • First run → same as fromBeginning: true
  • Subsequent runs → resume from last committed offset
  • Only new messages are processed

2. Offset-aware Catch-up using Admin API

Before starting the consumer, fetch the latest offsets (high-water mark) for each partition:

const latestOffsets = await fetchLatestOffsets(admin);

Inside eachMessage, track catch-up:

if (currentOffset >= latestOffset - 1n) {
  caughtUp.add(key);
}

if (caughtUp.size === latestOffsets.size) {
  resolve(); // all partitions caught up
}

Key Improvements

  • Deterministic completion
  • No arbitrary delays
  • Works under load and idle conditions

Additional Notes

  • Partitions with no messages (offset 0) are skipped
  • autoCommit: true ensures durability across restarts/crashes

Behaviour Comparison

Scenario Before After
First startup Replays all messages Replays all messages (identical)
Restart with no new messages Replays all messages again Exits immediately
Restart with 100 new messages Replays all messages Replays only 100 new messages
1M messages in Kafka 30+ min startup Seconds
Fresh cluster (no messages) 1s sleep then done Exits immediately
Stop condition Arbitrary 1s sleep Deterministic

Files Changed

  • backend/src/utils/warmupKafka.ts - full rewrite of warmup logic

Previously warmupKafkaCache() used a time-based consumer group ID
(warmup-consumer-<timestamp>) on every startup. Because Kafka had no
record of what was already processed, it always replayed every message
from offset 0 across all topics. Startup time grew linearly and
unboundedly with total message history.

Changes:
- Replace time-based groupId with stable 'ascent-warmup-stable' so
  Kafka tracks committed offsets across restarts. Only messages produced
  since the last startup are replayed on subsequent boots.
- Fetch the high-water-mark (latest offset) for every partition via the
  Admin API before starting the consumer. Stop the consumer as soon as
  all partitions are caught up instead of relying on an arbitrary 1s
  sleep, which was both unreliable under load and wasteful on idle.
- Exclude partitions with no messages (offset 0) so a fresh cluster
  skips warmup entirely.
- Enable autoCommit so progress is durable and survives crashes mid-warmup.
- Add per-step console logs so operators can observe warmup progress.

Fixes: 11byte#19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Performance: Kafka warmup replays all messages from offset 0 on every restart, startup time grows unboundedly

1 participant