Skip to content

feat(router): deduplicate near-simultaneous Kafka events before dispatch - #1

Open
mwisner wants to merge 1 commit into
mwisner/fix/kafka-consumer-client-leakfrom
mwisner/feat/kafka-event-dedup
Open

feat(router): deduplicate near-simultaneous Kafka events before dispatch#1
mwisner wants to merge 1 commit into
mwisner/fix/kafka-consumer-client-leakfrom
mwisner/feat/kafka-event-dedup

Conversation

@mwisner

@mwisner mwisner commented Aug 6, 2026

Copy link
Copy Markdown
Owner

What

Collapse near-simultaneous duplicate Kafka events at the earliest point — the poll loop in topicPoller, before Update() — so the whole downstream pipeline (BeforeEventsDispatch, subscription filtering, per-subscriber fan-out, _entities resolution) is skipped for dropped copies.

Stacked on wundergraph#3133 (mwisner/fix/kafka-consumer-client-leak). Base will be retargeted to main once wundergraph#3133 merges. This PR's diff is only the dedup change.

Why

The live-shows topics re-publish the same message many times. An audit log for a single show: 414 records, 37 distinct values; 91% were the identical contentless Show reference; a run of 70 identical records at the same timestamp. Cosmo has no native event dedup — every received record is processed and dispatched to every subscriber individually.

Correctness guardrail

A per-poller (per-subscription) time window separates the two duplicate classes:

  • same-instant bursts (Δt ≈ 0) → collapse
  • identical payloads re-emitted seconds apart (a contentless ref means "re-resolve again") → outside the window → delivered untouched

Each Subscribe owns its own kgo.Client and topicPoller goroutine, so the window is single-goroutine (no locking) and can never collapse across subscriptions.

Levers (env; default OFF — no behavior change until enabled)

Env Default Meaning
KAFKA_DEDUP_ENABLED false master switch
KAFKA_DEDUP_WINDOW_MS 50 suppression window; 0 = same-timestamp only
KAFKA_DEDUP_KEY content content (partition+key+value) | value | exact (+timestamp, strictest)
KAFKA_DEDUP_MAX_KEYS 4096 per-poller identity cap (bounds memory)

Observability

router.streams.received.messages still counts every received record; new router.streams.deduplicated.messages counts drops → collapse rate = deduplicated / received.

Tests

dedup_window_test.go: same-timestamp burst + 70-run collapse; ~2s re-emits survive; distinct payloads/partitions survive; WINDOW_MS=0 same-timestamp-only; each key mode; MAX_KEYS eviction; nil/disabled; mixed-fetch delivery count; env parsing + fallbacks. go vet + full pkg/pubsub/... and pkg/metric/... suites pass.

Do not merge before wundergraph#3133.

The live-shows Kafka topics re-publish the same message many times (an audit
log for one show showed 91% of 414 records were the identical contentless Show
reference, with a run of 70 at the same timestamp). Cosmo has no native event
dedup, so every received record runs the full pipeline — BeforeEventsDispatch,
subscription filtering, per-subscriber fan-out and _entities resolution — and
is dispatched to every subscriber individually.

Collapse near-simultaneous duplicates at the earliest point, the Kafka poll
loop, before Update(), so the whole downstream pipeline is skipped for dropped
copies. A per-poller (per-subscription) time window guards correctness: same-
instant bursts collapse while identical payloads re-emitted seconds apart —
which for a contentless entity reference mean "re-resolve again" — fall outside
the window and are delivered untouched.

Disabled by default and tunable via env (toggle in Helm, no rebuild):
  KAFKA_DEDUP_ENABLED   master switch (default false)
  KAFKA_DEDUP_WINDOW_MS suppression window; 0 = same-timestamp only (default 50)
  KAFKA_DEDUP_KEY       content | value | exact (default content)
  KAFKA_DEDUP_MAX_KEYS  per-poller identity cap, bounds memory (default 4096)

router.streams.received.messages still counts every received record; a new
router.streams.deduplicated.messages counter records drops so the collapse rate
is observable (deduplicated / received).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added the router label Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

Router-nonroot image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/mwisner/cosmo/router:sha-4ba99acb128113a17ec7704d8a22c92a98b29d9c-nonroot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant