Duplicate event rows observed during a Kafka consumer-group rebalance with multiple workers
维护者通常 1 天内回复
还没有人认领这个 Issue。
评估
- 难度
- 5/5
- 预计耗时
- 一周以上
- 新手友好度
- 35/100
- Issue 类型
- 缺陷
- 描述清晰度
- 基本清楚
- 活跃度
- 活跃
- 技术栈
- bun, clickhouse, kafka, redis, typescript
调研方向
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.
由索引模型根据 Issue 内容生成。
描述
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.
- 主要语言
- TypeScript
- 星标
- 7.1k
- 派生
- 516
- 平均合并
- 21 小时 36 分钟
- 30 天内合并 PR
- 22
环境准备
- 提供 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 没有贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
Openpanel-dev/openpanel 的其他 Issue
-
ClickHouse retry logs print CLICKHOUSE_URL with its password可能已有人在做 @ppjsw 于 1 天前认领。 未关闭
难度 1/5 1-3 小时 新手友好度 92/100
Openpanel-dev/openpanel#551 · 1 条评论 ·
维护者通常 1 天内回复
-
Self-hosted missing op1-replay.js可能重新可做 关联的 PR 已关闭且未合并。 未关闭
难度 2/5 1-3 小时 新手友好度 84/100
Openpanel-dev/openpanel#512 · 1 条评论 ·
维护者通常 1 天内回复
-
login page needs refinement可能重新可做 关联的 PR 已关闭且未合并。 未关闭
难度 2/5 1-3 小时 新手友好度 68/100
Openpanel-dev/openpanel#495 ·
维护者通常 1 天内回复
-
Session-grouped queries treat every sessionless event as one session (wrong results, ClickHouse OOM)未关闭
难度 4/5 3-5 天 新手友好度 52/100
Openpanel-dev/openpanel#575 ·
维护者通常 1 天内回复
-
Session replay: early chunks saved under the wrong session可能重新可做 关联的 PR 已关闭且未合并。 未关闭
难度 4/5 3-5 天 新手友好度 35/100
Openpanel-dev/openpanel#537 · 1 条评论 ·
维护者通常 1 天内回复
查看 Openpanel-dev/openpanel 的全部 Issue
相似的 Issue
-
[BUG] rag: sentence content can cross the reported sentence boundaries可能已有人在做 @LJK-ikun 今天认领。 未关闭bug
难度 2/5 1-3 小时 新手友好度 75/100
维护者通常 1 天内回复
-
bug needs-triage
难度 2/5 1-3 小时 新手友好度 70/100
维护者通常 1 天内回复
-
README: the Resource URI Schemas example's file://documents/* pattern only matches that literal URI未关闭
难度 1/5 1 小时以内 新手友好度 88/100
platformatic/mcp#361 ·
维护者通常 1 天内回复
-
frontend
难度 2/5 1-3 小时 新手友好度 78/100
维护者通常 1 天内回复
-
Push: sem service worker ou sem VAPID, o interruptor de Notificações fica ligado sem avisar nada未关闭
难度 2/5 1-3 小时 新手友好度 76/100
melgarafael/DeskcommCRM#3164 ·
维护者通常 1 天内回复