Kafka Consumer Builder
Build Kafka consumers and producers that survive production: rebalances, redeliveries, poison
messages, and restarts. Defaults assume at-least-once delivery, so consumers MUST be idempotent.
When to use
- "Write/scaffold a Kafka consumer or producer"
- "I'm getting duplicate processing / a consumer rebalance loop"
- "Add retries / a dead-letter queue to my consumer"
Hard rules (apply unless told otherwise)
- Unique consumer group id per logical service. Sharing a group id across different services
or unrelated deployments causes endless rebalance loops and stolen partitions — the single most
common Kafka incident. Name it
<service>-<purpose>.
- Idempotent processing. Derive an idempotency key from the message (e.g. an event id or a
business key). Check a dedupe store (DB unique constraint or Redis SETNX) before side effects.
- Manual offset commits after successful processing, not auto-commit. Commit only once the
work (and any DB write) is durable. Prefer committing in batches for throughput.
- Bounded retries + DLQ. Retry transient failures with exponential backoff and a max attempt
count; after that, publish to
<topic>.dlq with the error and original headers, then commit so
the partition is not blocked by one poison message.
- Graceful shutdown / rebalance. Stop polling on shutdown, finish the in-flight batch, commit,
then leave the group cleanly.
- Observability. Emit metrics for lag, processed, retried, dead-lettered; log with the message
key, partition, offset, and a request/correlation id.
Producer guidance
- Set a partition key so related events land on the same partition (ordering per key).
- Use idempotent producer settings (
enable.idempotence=true, acks=all) to avoid duplicates on retry.
- Include headers: event id, schema version,
correlation_id, timestamp.
Steps
- Confirm language (Go:
confluent-kafka-go or franz-go; Python: aiokafka/confluent-kafka),
topic(s), and the message schema.
- Generate the consumer with the rules above; wire the dedupe store and DLQ.
- Add a producer helper for emitting to the DLQ and any downstream topics.
- Add metrics + structured logging and a clean shutdown path.
- Include a short README documenting the group id, topics, DLQ name, and retry policy.
Reference — DLQ decision (pseudocode)
for msg in poll():
key = idempotency_key(msg)
if seen(key): commit(msg); continue
try:
process(msg) # side effects must be idempotent
mark_seen(key)
commit(msg)
except Transient as e:
if msg.attempt < MAX_RETRIES: retry_with_backoff(msg)
else: to_dlq(msg, e); commit(msg)
except Permanent as e:
to_dlq(msg, e); commit(msg)
Never let a single bad message block the partition. Never process side effects without a dedupe guard.
1---2name: kafka-consumer-builder3description: Use when building a Kafka (or Redpanda) consumer or producer and you want it to be production-safe rather than a naive read loop. Generates an idempotent consumer with explicit offset management, a dedicated consumer group id per service, at-least-once processing with deduplication, bounded retries with backoff, a dead-letter topic (DLQ) for poison messages, graceful rebalance handling, and structured logging/metrics. Also generates idempotent producers with keys for partition affinity. Trigger when the user asks to write/scaffold a Kafka consumer or producer, fix consumer rebalance loops or duplicate processing, or add a DLQ/retry strategy to an existing consumer.4license: MIT5---67# Kafka Consumer Builder89Build Kafka consumers and producers that survive production: rebalances, redeliveries, poison10messages, and restarts. Defaults assume **at-least-once** delivery, so consumers MUST be idempotent.1112## When to use13- "Write/scaffold a Kafka consumer or producer"14- "I'm getting duplicate processing / a consumer rebalance loop"15- "Add retries / a dead-letter queue to my consumer"1617## Hard rules (apply unless told otherwise)18191. **Unique consumer group id per logical service.** Sharing a group id across different services20 or unrelated deployments causes endless rebalance loops and stolen partitions — the single most21 common Kafka incident. Name it `<service>-<purpose>`.222. **Idempotent processing.** Derive an idempotency key from the message (e.g. an event id or a23 business key). Check a dedupe store (DB unique constraint or Redis SETNX) before side effects.243. **Manual offset commits after successful processing**, not auto-commit. Commit only once the25 work (and any DB write) is durable. Prefer committing in batches for throughput.264. **Bounded retries + DLQ.** Retry transient failures with exponential backoff and a max attempt27 count; after that, publish to `<topic>.dlq` with the error and original headers, then commit so28 the partition is not blocked by one poison message.295. **Graceful shutdown / rebalance.** Stop polling on shutdown, finish the in-flight batch, commit,30 then leave the group cleanly.316. **Observability.** Emit metrics for lag, processed, retried, dead-lettered; log with the message32 key, partition, offset, and a request/correlation id.3334## Producer guidance35- Set a partition **key** so related events land on the same partition (ordering per key).36- Use idempotent producer settings (`enable.idempotence=true`, acks=all) to avoid duplicates on retry.37- Include headers: event id, schema version, `correlation_id`, timestamp.3839## Steps401. Confirm language (Go: `confluent-kafka-go` or `franz-go`; Python: `aiokafka`/`confluent-kafka`),41 topic(s), and the message schema.422. Generate the consumer with the rules above; wire the dedupe store and DLQ.433. Add a producer helper for emitting to the DLQ and any downstream topics.444. Add metrics + structured logging and a clean shutdown path.455. Include a short README documenting the group id, topics, DLQ name, and retry policy.4647## Reference — DLQ decision (pseudocode)48```49for msg in poll():50 key = idempotency_key(msg)51 if seen(key): commit(msg); continue52 try:53 process(msg) # side effects must be idempotent54 mark_seen(key)55 commit(msg)56 except Transient as e:57 if msg.attempt < MAX_RETRIES: retry_with_backoff(msg)58 else: to_dlq(msg, e); commit(msg)59 except Permanent as e:60 to_dlq(msg, e); commit(msg)61```62Never let a single bad message block the partition. Never process side effects without a dedupe guard.