Learning · Event-driven & Kafka

Consuming correctly

Consumer groups, commits, idempotency, and ordering — processing Kafka without double charges or lost updates.

Consumers are where business side effects happen. Treat “message received” and “work finished” as different moments.

At-least-once is the default

Expect duplicates. Network retries, rebalances, and crash recovery will redeliver. Design handlers to be safe under replay.

Common approaches:

  • store processed event ids (or outbox ids) and skip duplicates
  • make writes upserts keyed by business identity
  • use DB constraints so a second apply is a no-op

“Exactly once” marketing usually means effectively once after you combine broker features with idempotent application logic.

Commit after side effects

Commit offsets only when the work is durable — or accept that a crash will reprocess. Committing early risks lost effects. Committing late risks duplicates. Prefer duplicates you can ignore over silent loss when money is involved.

Ordering within a key

If you keyed by entity id, one partition gives you order for that entity inside one consumer group. Do not parallelize the same key across threads in a way that reorders applies.

When order across entities does not matter, parallelize freely. When a saga depends on sequence for one payment, keep that payment’s events on one key and process them serially.

Poison messages

A bad payload should not block a partition forever. Define:

  • retry with backoff for transient errors
  • a dead-letter path after N failures
  • alerts on DLQ depth and oldest age
  • a runbook to fix and replay

Keep consumers boring

Thin handlers: deserialize, validate, apply idempotent state change, emit metrics. Heavy orchestration belongs in explicit workflows — not in an ever-growing listener class that calls six services synchronously per message.

← Event-driven & Kafka