Kafka Event-Driven Design Review¶
Quick Reference¶
| If you need to… | Go to |
|---|---|
| Understand what this skill covers | §1 Scope |
| Check mandatory prerequisites | §2 Mandatory Gates |
| Choose review depth | §3 Depth Selection |
| Handle incomplete context | §4 Degradation Modes |
| Evaluate design item by item | §5 Design Checklist |
| Choose partition and key strategy | §6 Partition Design |
| Avoid common Kafka mistakes | §7 Anti-Examples |
| Score the review result | §8 Scorecard |
| Format review output | §9 Output Contract |
| Deep-dive event schema patterns | references/event-schema-patterns.md |
| Understand consumer failure modes | references/consumer-failure-modes.md |
§1 Scope¶
In scope — Kafka event-driven architecture for production backend services:
- Topic design (naming, partition count, replication factor, retention)
- Partition key strategy (ordering guarantees, hot partition avoidance)
- Event schema design (Avro/Protobuf/JSON Schema, schema evolution, compatibility)
- Producer configuration (acks, retries, idempotence, transactional producers)
- Consumer group design (assignment strategy, rebalance handling, commit strategy)
- Idempotent consumption (deduplication, idempotency keys, exactly-once semantics)
- Dead letter queue (DLQ) and retry patterns
- Backpressure and consumer lag management
- Schema Registry integration and compatibility modes
Out of scope — delegate to dedicated skills:
- Kafka cluster operations, broker config, ZooKeeper/KRaft migration → Kafka ops
- Application code changes unrelated to Kafka →
go-code-reviewer - General API design →
api-design
§2 Mandatory Gates¶
Execute gates sequentially. Each gate has a STOP condition.
Gate 1: Context Collection¶
| Item | Why it matters | If unknown |
|---|---|---|
| Kafka version (2.x / 3.x) | Exactly-once, cooperative rebalance features vary | Assume 2.8 (conservative) |
| Schema format (Avro / Protobuf / JSON / none) | Determines evolution strategy and registry needs | Must clarify |
| Ordering requirement | Per-entity? Per-tenant? Global? None? | Must clarify — drives partition key |
| Delivery guarantee needed | At-most-once / At-least-once / Exactly-once | Assume at-least-once |
| Throughput estimate (events/sec) | Determines partition count and consumer scaling | Ask; assume moderate |
| Consumer count / group topology | Single consumer group? Multiple? Fan-out? | Must clarify |
| Retention policy | Time-based / size-based / compacted | Assume 7 days time-based |
| Schema Registry | Confluent / AWS Glue / Apicurio / none | Ask; critical for evolution |
STOP: Cannot determine what events are being produced/consumed (no domain context). Clarify before proceeding.
PROCEED: At least event type, ordering requirement, and delivery guarantee are known.
Gate 2: Scope Classification¶
| Mode | Trigger | Output |
|---|---|---|
| review | User provides existing Kafka config/code | Safety analysis with findings |
| design | User describes event-driven requirements | Complete topic + schema + consumer design |
| troubleshoot | User reports issues (lag, duplication, ordering) | Root cause + fix plan |
STOP: Not Kafka-related (e.g., REST API design). Redirect to appropriate skill.
PROCEED: Kafka event-driven intent confirmed.
Gate 3: Risk Classification¶
| Risk | Definition | Required action |
|---|---|---|
| SAFE | Single topic, simple consumer, at-least-once | Standard review |
| WARN | Multi-topic transactions, schema evolution, exactly-once | Off-peak deployment + monitoring |
| UNSAFE | Partition key change on live topic, consumer group migration, schema breaking change | Staged rollout + rollback plan mandatory |
STOP: Any UNSAFE item without mitigation plan.
PROCEED: Every component has risk level and mitigation.
Gate 4: Output Completeness¶
Before delivering output, verify all §9 Output Contract sections present. §9.9 Uncovered Risks must never be empty.
§3 Depth Selection¶
| Depth | When to use | Gates | References to load |
|---|---|---|---|
| Lite | Single producer/consumer pair, simple schema | 1–4 | None |
| Standard | Multi-consumer topology, schema evolution, DLQ design | 1–4 | event-schema-patterns.md |
| Deep | Cross-service event mesh, exactly-once, CQRS/ES pattern | 1–4 | Both reference files |
Force Standard or higher when any signal appears: schema evolution requirement, exactly-once semantics, multi-consumer-group fan-out, partition key redesign, consumer group migration, compacted topics for event sourcing.
§4 Degradation Modes¶
When context is incomplete, degrade gracefully — never fabricate assumptions about ordering requirements.
| Available context | Mode | What you can do | What you cannot do |
|---|---|---|---|
| Full (version, schema, ordering, delivery, throughput) | Full | Complete design with quantified guarantees | — |
| Event type + ordering known, infra unknown | Degraded | Schema + consumer design; flag infra unknowns | Partition count, replication recommendations |
| Only code snippets, no architecture context | Minimal | Static review of producer/consumer patterns | Full topology design |
| No code (greenfield design) | Planning | Propose event architecture from requirements | Review existing implementation |
Hard rule: Never claim "exactly-once" without verifying transactional producer + consumer read-committed isolation + idempotent processing. In Degraded/Minimal mode, flag "delivery guarantee unverified" in §9.9.
§5 Design Checklist¶
Execute every item. Mark PASS / WARN / FAIL with evidence.
5.1 Topic Design¶
-
Topic naming convention — follow
{domain}.{entity}.{event-type}or{team}.{service}.{event}pattern. Names should be greppable, meaningful, and avoid generic names like "events" or "messages". -
Partition count sized for throughput — each partition is the unit of parallelism. Rule of thumb: partitions ≥ max expected consumer instances. Over-partitioning wastes resources; under-partitioning caps throughput. Partition count cannot be decreased (only increased, which breaks key-based ordering).
-
Replication factor ≥ 3 for production topics —
min.insync.replicas = 2withacks = allensures no data loss on single broker failure. RF=1 is unacceptable for any non-ephemeral data. -
Retention policy matches use case — time-based (default 7 days) for event streaming; compacted for entity-state topics (latest value per key retained indefinitely); infinite retention for event sourcing.
5.2 Producer Design¶
-
acks=all + retries + enable.idempotence=true — this is the minimum safe producer config.
acks=1risks data loss on leader failure.acks=0is fire-and-forget. Idempotent producer (Kafka 0.11+) prevents duplicate writes from retries. -
Partition key chosen for ordering + distribution — key determines which partition an event goes to. Events with the same key are ordered. Common keys: entity ID (order_id, user_id). Avoid: timestamp (hot partition), null (round-robin = no ordering), high-cardinality unbounded (too many partitions).
-
Event schema includes metadata — every event should carry:
event_id(UUID),event_type,timestamp,source_service,correlation_id,schema_version. Without these, debugging and deduplication become impossible. -
Transactional producer for multi-topic atomicity — if producing to multiple topics must be atomic (e.g., event + outbox), use Kafka transactions (
initTransactions,beginTransaction,commitTransaction). Without transactions, partial writes create inconsistency.
5.3 Consumer Design¶
-
Idempotent consumption — at-least-once delivery means consumers WILL receive duplicates (after rebalance, retry, or producer retry). Every consumer must handle duplicates: deduplication by
event_id, idempotent database operations (INSERT ON CONFLICT), or exactly-once via transactional consumer. -
Commit strategy explicit —
enable.auto.commit=true(default) commits offsets periodically, risking reprocessing on crash. For at-least-once: manual commit after processing. For exactly-once: commit offsets within the transaction that processes the event. -
Dead letter queue (DLQ) for poison messages — messages that repeatedly fail processing must be routed to a DLQ topic instead of blocking the partition. Without DLQ, a single bad message blocks all subsequent messages in that partition indefinitely.
-
Consumer lag monitoring —
consumer_lag(latest offset - committed offset) per partition is the primary health metric. Alert on sustained lag > threshold. Usekafka-consumer-groups.shor metrics exporter. Lag = events waiting to be processed.
5.4 Schema Evolution & Operations¶
-
Schema compatibility mode set — Schema Registry supports: BACKWARD (new reader, old data), FORWARD (old reader, new data), FULL (both). Choose based on deployment strategy. BACKWARD_TRANSITIVE is safest for most cases. Breaking changes require a new topic.
-
Backpressure handling defined — what happens when consumers can't keep up? Options: scale consumers (up to partition count), increase batch size, apply rate limiting on producer, drop low-priority events. "Consumer crashes under load" is not a strategy.
§6 Partition Design (Standard + Deep)¶
Quick decision guide — for schema patterns load references/event-schema-patterns.md.
| Ordering need | Partition key | Example |
|---|---|---|
| Per-entity ordering | Entity ID | order_id → all events for order 123 in same partition |
| Per-tenant ordering | Tenant ID | tenant_id → tenant isolation per partition |
| Per-user ordering | User ID | user_id → user action sequence preserved |
| No ordering needed | null (round-robin) | Metrics, logs, analytics events |
| Global ordering | Single partition | Only if throughput is very low (<100 events/sec) |
Hot partition detection and mitigation¶
- Symptom: one partition has 10x the events of others; consumer for that partition lags
- Cause: skewed partition key (e.g., one tenant produces 90% of events)
- Fix: composite key (
tenant_id + entity_id), or custom partitioner that spreads hot keys across N partitions while maintaining per-entity ordering
§7 Anti-Examples¶
AE-1: Producer with acks=1 for critical business events¶
// WRONG: acks=1 — leader acknowledges before replication; data lost on leader failure
producer, _ := sarama.NewSyncProducer(brokers, config)
// config.Producer.RequiredAcks = sarama.WaitForLocal // acks=1
// RIGHT: acks=all + idempotent
config.Producer.RequiredAcks = sarama.WaitForAll
config.Producer.Idempotent = true
config.Net.MaxOpenRequests = 1
AE-2: Consumer without idempotency handling¶
// WRONG: processes event and assumes it won't be delivered again
func handleOrderCreated(event OrderCreated) {
db.Insert(event.Order) // duplicate delivery → duplicate insert → constraint violation or double-charge
}
// RIGHT: idempotent processing
func handleOrderCreated(event OrderCreated) {
db.Exec("INSERT INTO orders ... ON CONFLICT (id) DO NOTHING", event.Order)
}
AE-3: No dead letter queue — poison message blocks partition¶
// WRONG: bad message causes infinite retry loop, blocking all events behind it
func consume(msg *sarama.ConsumerMessage) {
if err := process(msg); err != nil {
log.Error(err)
// message is not committed → redelivered forever
}
}
// RIGHT: route to DLQ after N retries
if retryCount >= maxRetries {
producer.Send(dlqTopic, msg)
consumer.CommitMessage(msg) // advance past the poison message
}
AE-4: Null partition key — loses ordering guarantee¶
// WRONG: null key → round-robin across partitions → order events scattered
producer.SendMessage(&sarama.ProducerMessage{
Topic: "order.events",
Value: sarama.ByteEncoder(data),
// Key is nil → no ordering guarantee
})
// RIGHT: use entity ID as partition key
producer.SendMessage(&sarama.ProducerMessage{
Topic: "order.events",
Key: sarama.StringEncoder(order.ID),
Value: sarama.ByteEncoder(data),
})
AE-5: Schema change without compatibility check¶
// WRONG: removed required field — breaks all existing consumers
// v1: {"order_id": "123", "amount": 99.50, "currency": "USD"}
// v2: {"order_id": "123", "amount": 99.50} // removed currency!
// RIGHT: add new optional fields, never remove required ones (BACKWARD compatible)
AE-6: Kafka issue reported as application logic bug¶
-- WRONG: "Bug: some orders processed twice causing double charge"
-- This is a consumer idempotency issue, not application logic.
-- RIGHT: "Consumer lacks idempotency: duplicate Kafka delivery causes double processing"
Extended anti-examples (AE-7 through AE-13) in references/consumer-anti-examples.md.
§8 Kafka Design Scorecard¶
Critical — any FAIL means overall FAIL¶
- [ ] Producer uses
acks=all+enable.idempotence=truefor non-ephemeral events - [ ] Consumer handles duplicate delivery (idempotent processing or exactly-once)
- [ ] Dead letter queue exists for poison messages (no infinite retry loops)
Standard — 4 of 5 must pass¶
- [ ] Partition key matches ordering requirement (not null for ordered events)
- [ ] Schema includes event metadata (event_id, event_type, timestamp, source)
- [ ] Schema compatibility mode configured in Schema Registry
- [ ] Consumer lag monitoring with alert threshold defined
- [ ] Commit strategy explicitly chosen (auto-commit disabled for at-least-once)
Hygiene — 3 of 4 must pass¶
- [ ] Topic naming follows convention (
{domain}.{entity}.{event-type}) - [ ] Replication factor ≥ 3 with
min.insync.replicas = 2 - [ ] Backpressure strategy defined (scale / rate-limit / drop)
- [ ] Retention policy matches use case (time-based / compacted / infinite)
Verdict: X/12; Critical: Y/3; Standard: Z/5; Hygiene: W/4. PASS requires: Critical 3/3 AND Standard ≥4/5 AND Hygiene ≥3/4.
§9 Output Contract¶
Every design review MUST produce these sections. Write "N/A — [reason]" if inapplicable.
### 9.1 Context Gate
| Item | Value | Source |
### 9.2 Depth & Mode
[Lite/Standard/Deep] × [review/design/troubleshoot] — [rationale]
### 9.3 Risk Assessment
| Component | Risk | Notes |
### 9.4 Architecture Design (Standard/Deep; "N/A — Lite" for Lite)
- Topic topology + partition strategy
- Producer + consumer configuration
- Schema design + evolution strategy
### 9.5 Implementation (topic config, producer/consumer code patterns)
### 9.6 Validation Plan
- End-to-end event flow test
- Duplicate delivery test
- Schema evolution test (produce v2, consume with v1 reader)
### 9.7 Failure Handling
- DLQ routing + monitoring
- Consumer rebalance behavior
- Broker failure recovery
### 9.8 Monitoring & Alerts
- Consumer lag per partition
- DLQ message rate
- Producer error rate, batch size, latency
### 9.9 Uncovered Risks (MANDATORY — never empty)
| Area | Reason | Impact | Follow-up |
Volume rules: - FAIL: always fully detailed - WARN: up to 10; overflow to §9.9 - PASS: summary only - §9.9 minimum: document all assumptions (especially delivery guarantee if unverified)
Scorecard summary (append after §9.9):
Scorecard: X/12 — Critical Y/3, Standard Z/5, Hygiene W/4 — PASS/FAIL
Data basis: [full context | degraded | minimal | planning]
§10 Reference Loading Guide¶
| Condition | Load |
|---|---|
| Standard or Deep depth | references/event-schema-patterns.md |
| Deep depth, or consumer failure/lag signals | references/consumer-failure-modes.md |
| Extended anti-example matching | references/consumer-anti-examples.md |