Kafka Streaming skill for Claude Code

Keyed order, durable writes, idempotent consumers. Claude maps ordering needs to message keys, sizes partitions for peak load, and configures replication with min.insync.replicas and idempotent producers. Consumers commit only after processing, dedupe in the sink, and route poison messages to a dead-letter topic. It explains where exactly-once actually applies and checks schema changes against registry compatibility.

  • namekafka-streaming
  • size200 lines · ≈2,350 tokens
  • files1
  • licenseMIT
  • updated

What Claude sees before loading it

This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.

description: Designs and debugs Apache Kafka producers, consumers, and topics — partition count and key choice for ordering, replication and min.insync.replicas, idempotent producers, manual offset commits for at-least-once processing, idempotent consumers, dead-letter topics, consumer lag and rebalance troubleshooting, the real limits of exactly-once, and Schema Registry compatibility. Use when creating Kafka topics, writing producer or consumer code, when consumer lag grows, messages are lost, duplicated, or out of order, or when evolving Avro or Protobuf schemas.

Try asking

  • Design the Kafka topic and consumer for order events that must stay in order per customer
  • Consumer lag keeps growing on one partition. Help me figure out why
  • We see duplicate payments from our Kafka consumer after deploys. Fix it

Use it when

  • You are creating topics or writing producer and consumer code
  • Consumer lag grows or consumers rebalance constantly
  • Messages are lost, duplicated, or arrive out of order
  • An Avro, Protobuf, or JSON schema needs to evolve safely

Skip it when

How to check the result

Failure drills pass: killing a consumer creates no duplicates in the sink, stopping one broker loses no acknowledged writes, a malformed message lands in the DLQ, and lag stays bounded at peak.

The common mistake it prevents

Committing offsets before processing finishes, which silently drops messages whenever a consumer crashes or rebalances mid-batch.

~/.claude/skills/kafka-streaming/SKILL.md

--- name: kafka-streaming description: Designs and debugs Apache Kafka producers, consumers, and topics — partition count and key choice for ordering, replication and… license: MIT ---

A Kafka design is sound when ordering requirements map to message keys, no acknowledged write can be lost to a single broker failure, consumers commit offsets only after processing and tolerate redelivery, poison messages go to a dead-letter topic instead of blocking a partition, and schema changes are checked for compatibility before deploy.

Ground rules

  • Ordering exists only within a partition. Choose the key so that everything that must stay ordered shares a key.
  • Durability: replication factor 3, min.insync.replicas=2, producer acks=all.
  • Assume at-least-once delivery. Make consumers idempotent (dedupe key, upsert, or processed-offset table) rather than relying on exactly-once.
  • Commit offsets after processing succeeds, never before.
  • Partition count can grow but not shrink, and growing it remaps keys to partitions. Size it up front.
  • Offset resets, topic deletion, and retention changes on production topics require user confirmation; always run --dry-run first.

Workflow

- [ ] 1. Inspect cluster version, topic configs, consumer groups, lag
- [ ] 2. Define the event: key, value schema, ordering need, retention
- [ ] 3. Size partitions and set topic configs
- [ ] 4. Producer: idempotent, acks=all, delivery callbacks, batching
- [ ] 5. Consumer: manual commits, idempotent handling, DLQ, rebalance safety
- [ ] 6. Register schema with a compatibility mode
- [ ] 7. Load test; verify lag, ordering, and failure behavior

1. Inspect

BS=broker1:9092
kafka-topics.sh --bootstrap-server $BS --describe --topic orders
kafka-configs.sh --bootstrap-server $BS --describe --entity-type topics --entity-name orders
kafka-consumer-groups.sh --bootstrap-server $BS --describe --group billing    # LAG per partition
kafka-console-consumer.sh --bootstrap-server $BS --topic orders --from-beginning \
  --max-messages 5 --property print.key=true --property print.timestamp=true

kcat -b $BS -t orders -C -c 5 -f '%k %p %o %s\n' is a handy alternative.

2–3. Topic design

DecisionGuidance
KeyEntity whose events must stay ordered (order_id, account_id). Null key = no ordering
Hot keysOne key with most traffic overloads one partition; add a sub-key or salt only if per-key order can be relaxed
PartitionsMax consumer parallelism = partitions. Size for peak throughput / per-consumer throughput, with 2–3x headroom (e.g. 12, 24, 48)
RetentionTime-based for events (retention.ms); cleanup.policy=compact for latest-state-per-key topics
Message sizeKeep under ~1 MB (default limit); store large payloads in object storage and send a reference
kafka-topics.sh --bootstrap-server $BS --create --topic orders \
  --partitions 24 --replication-factor 3 \
  --config min.insync.replicas=2 --config retention.ms=604800000 --config compression.type=zstd

4. Producer (confluent-kafka Python; same configs in other clients)

from confluent_kafka import Producer

producer = Producer({
    "bootstrap.servers": "broker1:9092",
    "acks": "all",
    "enable.idempotence": True,     # no duplicates or reordering from producer retries
    "linger.ms": 10,
    "compression.type": "zstd",
    "delivery.timeout.ms": 120000,
})

def on_delivery(err, msg):
    if err is not None:
        log.error("delivery failed", extra={"key": msg.key(), "error": str(err)})
        # route to retry/outbox; never ignore

producer.produce("orders", key=order_id.encode(), value=payload, on_delivery=on_delivery)
producer.poll(0)                    # serve delivery callbacks
# on shutdown:
producer.flush(30)

Writing to a database and publishing an event in one action: use the transactional outbox pattern (write the event to an outbox table in the same DB transaction; a relay or CDC tool like Debezium publishes it). Dual writes lose events on crashes.

5. Consumer

from confluent_kafka import Consumer, KafkaError

consumer = Consumer({
    "bootstrap.servers": "broker1:9092",
    "group.id": "billing",
    "enable.auto.commit": False,
    "auto.offset.reset": "earliest",
    "partition.assignment.strategy": "cooperative-sticky",
    "max.poll.interval.ms": 300000,
})
consumer.subscribe(["orders"])
try:
    while running:
        msg = consumer.poll(1.0)
        if msg is None:
            continue
        if msg.error():
            if msg.error().code() != KafkaError._PARTITION_EOF:
                log.error("consumer error", extra={"error": str(msg.error())})
            continue
        try:
            handle(msg)                              # idempotent: upsert keyed by event id
        except PermanentError as exc:
            send_to_dlq(msg, reason=str(exc))        # orders.dlq with original headers + error
        consumer.commit(message=msg, asynchronous=False)
finally:
    consumer.close()                                 # leaves the group cleanly, commits nothing extra
ConcernRule
Delivery semanticsProcess then commit = at-least-once. Commit then process = at-most-once (data loss on crash)
DuplicatesDedupe by event ID in the sink (unique constraint, upsert, or processed-ID table)
Poison messageAfter N retries, publish to <topic>.dlq with error metadata and move on
Transient failureRetry with backoff in-process for short outages; pause the partition for long ones
Slow processingKeep each poll loop under max.poll.interval.ms, or the consumer is kicked out and its partitions rebalance
Rebalance churnCooperative-sticky assignor; static membership (group.instance.id) for stable pods; the new KIP-848 protocol (group.protocol=consumer, Kafka 4.0+) where clients support it

Exactly-once: what it really covers

Kafka transactions (transactional.id, consumers with isolation.level=read_committed) give exactly-once for read-from-Kafka, process, write-to-Kafka loops (Kafka Streams processing.guarantee=exactly_once_v2). They do not make side effects in databases, emails, or HTTP calls exactly-once. For those, use idempotent sinks or the outbox pattern.

6. Schema Registry

CompatibilityAllowsUse when
BACKWARD (default)New schema reads old data: delete fields, add optional fields with defaultsConsumers upgrade before producers
FORWARDOld schema reads new data: add fields, delete optional fieldsProducers upgrade first
FULLBothIndependent deploys, long retention
*_TRANSITIVEChecked against all versions, not just the lastReplay from old offsets

Safe changes: add a field with a default; make a field optional. Breaking: rename, change type, remove a required field. Test compatibility in CI against the registry’s compatibility endpoint (or the Maven/Gradle plugin) before deploy.

7. Verify

  • Lag: kafka-consumer-groups.sh --describe --group billing stays bounded at peak; alert on lag in time (seconds behind), not just record count.
  • Ordering: for a sample key, consumed offsets and business sequence numbers are monotonic.
  • Failure drills: kill a consumer mid-batch (messages redelivered, no duplicates in sink); stop one broker (producer continues with acks=all, no acknowledged loss); publish a malformed message (lands in DLQ, partition keeps flowing).

Lag and incident triage

SymptomLikely causeAction
Lag grows on all partitionsConsumers too slow or too fewScale consumers up to partition count; optimize handler; batch sink writes
Lag on one partition onlyHot key or poison messageInspect that partition’s head message; DLQ it; reconsider key
Constant rebalancingPoll loop exceeds max.poll.interval.ms, pods restartingSmaller max.poll.records, faster handler, static membership
Duplicates in sinkRedelivery after crash/rebalanceIdempotent sink
Missing messagesAuto-commit before processing, acks=1, or retention expired before consumptionFix commit order, acks, retention
Reprocess neededBug fixed, replay requiredStop group, --reset-offsets --to-datetime ... --dry-run, review, then --execute (confirm)

Gotchas

  • Adding partitions changes which partition a key maps to; per-key order breaks across the change.
  • auto.offset.reset only applies when the group has no committed offset; it is not a replay switch.
  • KafkaJS is no longer actively maintained; for new Node services prefer Confluent’s JavaScript client or another maintained library.
  • ZooKeeper is gone in Kafka 4.x (KRaft only); old --zookeeper CLI flags no longer exist.
  • Compacted topics keep the latest value per key but not immediately; consumers still see older values until compaction runs.
  • Large consumer groups across many topics make every rebalance stop-the-world on the classic protocol.

Output

**Topic:** orders — 24 partitions, RF 3, min.insync.replicas 2, 7-day retention, zstd
**Key:** order_id (per-order ordering); schema orders-value v4, BACKWARD compatible
**Producer:** idempotent, acks=all, outbox relay from orders DB
**Consumer (billing):** manual commit after upsert by event_id; DLQ orders.dlq after 5 retries
**Drills:** consumer kill -> 0 duplicates in sink; broker stop -> 0 lost acks; poison msg -> DLQ
**Lag at peak (3k msg/s):** < 2 s

About this skill

Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.

esc