Duplicate event rows observed during a Kafka consumer-group rebalance with multiple workers
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Idoneità per principianti
- 35/100
- Tipo di issue
- Bug
- Chiarezza
- Abbastanza chiara
- Stato di attività
- Attiva
- Stack tecnologico
- bun, clickhouse, kafka, redis, typescript
- Ambito
- backend, data-engineering, databases
Direzione di ricerca
Start with packages/core/src/modules/ingest/src/consumer.ts, especially the message processing, heartbeat, and offset resolution flow around the cited lines. Trace how rebalance or heartbeat failures interact with downstream writes and pending handlers; the issue’s reproduction and SQL query provide a way to compare event rows with unique input sequences. Done means clarifying the delivery contract and reliably preventing duplicate analytics effects without losing durable events, with a rebalance integration test.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Summary
On main 7f6f7f9e2b978bd0472807f586d65928f793d08c (including #560), replaying a fixed Kafka backlog into empty Redis and ClickHouse produced 108,327 business-event rows for 100,000 unique input events. Worker logs report a consumer-group rebalance during startup. Kafka lag and all buffers eventually reached zero.
This was not an intentional offset reset against an already-populated sink: each run used a fresh consumer group, empty Redis and empty ClickHouse. The API and load generator were stopped during replay. Rebalancing itself is expected; the concern is duplicate application-visible events following redelivery.
Environment
- OpenPanel revision:
7f6f7f9e2b978bd0472807f586d65928f793d08c. - KafkaJS 2.2.4, Bun runtime (application image built locally from that revision).
- Redpanda v25.1.9, 25 topic partitions, replication factor 1, 1 broker CPU / 1 GB broker memory configuration.
- ClickHouse 25.10.2.65, Redis 7.2.5.
- Shared local VM: 6 vCPU / 24 GB; each worker limited to 2 CPU / 4 GB; ClickHouse limited to 2 CPU / 8 GB.
- Two worker containers launched via one Compose
upcommand; their startup log timestamps differ by approximately one second. group_initial_rebalance_delay=0in the failing run.
Buffer settings:
EVENT_BUFFER_BATCH_SIZE=10000
EVENT_BUFFER_CHUNK_SIZE=10000
SESSION_BUFFER_BATCH_SIZE=20000
# SESSION_BUFFER_CHUNK_SIZE unset
PROFILE_BUFFER_BATCH_SIZE=500
PROFILE_BUFFER_CHUNK_SIZE=1000
CRON_CONCURRENCY=6
BUFFER_CH_INSERT_CONCURRENCY=5
Schema disclosure: these replay tests used a test-only correction to the sessions sorting key, adding id to avoid a separately reproduced cross-session collapsing problem. The events table and application ingestion code were unchanged. This issue concerns duplicate rows in events, not that separate session issue.
Reproduction scenario
- Prepare an isolated Kafka topic containing 100,000 tracked events. Use 10,000 distinct device/profile identities, 10 events per identity, and a globally unique
properties.seqfrom 0 to 99,999. The fixture was originally accepted through the API and its initial completed ingestion had 100,000 rows and 100,000 distinct sequences. - Stop the API, producer/load generator and other consumers of the test group. Verify topic offsets: all 25 partitions start at 0 and the sum of high watermarks is 100,000.
- Use a new consumer group and empty Redis/ClickHouse. Let the group start at the oldest messages. Set Redpanda
group_initial_rebalance_delay=0for the reproduction. - Start two workers against this backlog. The first can begin processing before the second finishes joining. Capture the group rebalance logs. A controlled follow-up can explicitly add a second worker while the first processes an uncommitted batch; that variation has not yet been tested here.
- Wait for Kafka lag and event/session/profile buffers to reach zero, then count only the fixture's business-event name (exclude generated
session_start).
SELECT
count() AS event_rows,
uniqExact(properties['seq']) AS unique_input_events
FROM openpanel.events
WHERE name = 'retest20_keyfix100k_chunk10k';
Observations
| Run | Workers | Initial rebalance delay | Business rows | Unique input sequences | Final lag/buffers |
|---|---|---|---|---|---|
| Cold-start replay | 2 | 0 ms | 108327 | 100000 | All zero |
| Control | 2 | 5000 ms | 100000 | 100000 | All zero |
| Control | 3 | 5000 ms | 100000 | 100000 | All zero |
The failing run has 8,327 excess rows (8.327%). Both controls also passed session/profile reconciliation. These are individual runs, not a measured statistical reproduction rate. A five-second initial delay reduced startup overlap in these controls; it is not a general fix for later scaling, restarts, broker failover or crashes.
Representative KafkaJS logs from the failing run:
2026-10-20T03:03:37.586Z [Connection] Response Heartbeat(key: 12, version: 3)
error: The group is rebalancing, so a rejoin is needed
...
2026-10-20T03:03:43.074Z [Runner] The group is rebalancing, re-joining
groupId: oprt-replay100k_w2-20261020
Relevant code / working hypothesis
consumer.ts at the tested revision:
await processMessage(m, batch.partition);
processed.add(m.offset);
processedCount += 1;
if (processedCount % HEARTBEAT_EVERY === 0) {
await heartbeat();
}
// After all per-key groups finish:
if (await bufferedEventsAreDurable(batch.partition, closeDurabilityWindow)) {
for (const m of batch.messages) {
if (!processed.has(m.offset)) break;
resolveOffset(m.offset);
}
}
processMessage can produce downstream effects before the batch's offsets are resolved/committed. If a heartbeat rejects because of a rebalance, or ownership changes before offsets are committed, Kafka can redeliver messages whose effects already reached Redis/ClickHouse. The per-key handlers use Promise.all; a rejection also deserves review for other handlers that may still be running.
This is a working hypothesis based on code and correlated logs, not a proven message-by-message trace. We have not yet correlated every duplicate with its original Kafka partition/offset. The source explicitly prefers replay over loss when durability cannot be confirmed; that is sensible at-least-once behavior, but duplicate events affect analytics unless downstream handling is idempotent.
Questions / suggested follow-up
- Is at-least-once delivery with application-visible duplicate event counts the intended contract during rebalances? If so, could the limitations and recommended deduplication strategy be documented?
- Could an integration test trigger a rebalance after downstream side effects but before offset commit, checking for both loss and duplicate analytics effects?
- Can event ingestion and session/profile side effects be made idempotent under replay, using a stable event identifier or topic/partition/offset identity?
- Please avoid committing offsets before durable effects, or adding a standalone dedup marker before writes: either can introduce data loss. Any deduplication mechanism needs crash-safe coordination with the effects it protects.
No production consumer offsets were reset. No proposed code fix is claimed here. The session sorting-key issue and an earlier independent EPIPE observation should be tracked separately rather than assumed to have this same cause.
- Lingua principale
- TypeScript
- Stelle
- 7.1k
- Fork
- 516
- Merge medio
- 21h 36m
- PR unite (30g)
- 22
Preparare l'ambiente
- Include un Dockerfile o un file Docker Compose
- Nessun modello di pull request
- Nessuna guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di Openpanel-dev/openpanel
-
ClickHouse retry logs print CLICKHOUSE_URL with its passwordForse già presa @ppjsw l’ha presa oggi. Aperta
Difficoltà 1/5 1-3 ore Idoneità per principianti 92/100
Openpanel-dev/openpanel#551 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Self-hosted missing op1-replay.jsForse di nuovo libera Una pull request per questa issue è stata chiusa senza essere unita. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
Openpanel-dev/openpanel#512 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
login page needs refinementForse di nuovo libera Una pull request per questa issue è stata chiusa senza essere unita. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
Openpanel-dev/openpanel#495 ·
I maintainer di solito rispondono entro 1 giorno
-
Session-grouped queries treat every sessionless event as one session (wrong results, ClickHouse OOM)Aperta
Difficoltà 4/5 3-5 giorni Idoneità per principianti 52/100
Openpanel-dev/openpanel#575 ·
I maintainer di solito rispondono entro 1 giorno
-
Session replay: early chunks saved under the wrong sessionForse di nuovo libera Una pull request per questa issue è stata chiusa senza essere unita. Aperta
Difficoltà 4/5 3-5 giorni Idoneità per principianti 35/100
Openpanel-dev/openpanel#537 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di Openpanel-dev/openpanel
Issue simili
-
Area/Cloud Needs Triage Type/Bug
Difficoltà 1/5 1-3 ore Idoneità per principianti 86/100
wso2/product-integrator#2847 ·
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 67/100
bluesky-social/social-app#12118 ·
I maintainer di solito rispondono entro 1 giorno
-
enhancement
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 85/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 77/100
heygen-com/hyperframes#6021 ·
I maintainer di solito rispondono entro 1 giorno
-
Edit: Tele Posadas HDApertacheck:passed streams:edit
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
I maintainer di solito rispondono entro 1 giorno