Apache kafka
Профессиональные Data Engineering Agent Skills для разработки AI Agentic Data Platform
npx -y skills add ivanshamaev/de-agent-skills --skill apache_kafkaAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 13 stars13 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Apache Kafka — topics, partitions, consumer groups, producer/consumer configuration (acks, idempotence, compression, commit strategies), exactly-once semantics, Kafka Connect (source/sink connectors, SMTs, DLQ), Schema Registry, consumer lag monitoring, CLI operations, Python confluent-kafka examples, partition strategy, replication, retention
SKILL.md
24.5 KB, as published. Nobody here has run it
Apache Kafka
When to Use
Load this skill when the user needs to:
- Design Kafka topics, choose partition count and replication factor
- Configure producers (acks, idempotence, batching, compression)
- Configure consumers (commit strategies, rebalancing, exactly-once)
- Set up Kafka Connect source/sink connectors
- Handle errors with Dead Letter Queues
- Monitor consumer lag and cluster health
- Write Python producers/consumers with
confluent-kafka
Core Concepts
Broker cluster (3+ nodes)
└─ Topic (logical stream of records)
└─ Partition 0 [offset 0, 1, 2, 3, ...] ← replicated to follower brokers
└─ Partition 1 [offset 0, 1, 2, ...]
└─ Partition N ...
Producer → writes to partitions (by key hash or round-robin)
Consumer Group → each partition consumed by exactly one consumer in the group
N partitions ÷ M consumers = M≤N for full parallelism
Offset — position of a consumer in a partition; committed to __consumer_offsets topic.
Leader replica — handles reads and writes; followers replicate.
ISR (In-Sync Replicas) — replicas that are caught up with the leader.
Topic Design
Partition Count
Partitions = max(target_throughput_MB/s ÷ per_partition_throughput, desired_consumer_parallelism)
Rules of thumb:
- Start with 6–12 partitions for most topics; increase later (but keyed topics can't repartition without rekeying).
- Never set partitions < number of consumers in the consuming group.
- Avoid over-partitioning — each partition has file handles and memory overhead on every broker.
Replication & Durability
# Create topic with production settings
kafka-topics.sh --bootstrap-server broker:9092 --create \
--topic orders \
--partitions 12 \
--replication-factor 3 \
--config min.insync.replicas=2 \
--config retention.ms=604800000 \
--config compression.type=snappy
| Parameter | Recommended | Notes |
|---|---|---|
replication.factor | 3 | Survive 1 broker loss |
min.insync.replicas | 2 | With acks=all ensures at least 2 copies |
unclean.leader.election.enable | false | Prevent data loss from lagging replica becoming leader |
retention.ms | 7 days (604800000) | Or -1 for infinite; override per topic |
retention.bytes | -1 or explicit | Size-based retention per partition |
cleanup.policy | delete or compact | compact for changelog/event-sourcing topics |
compression.type | snappy or lz4 | Set on topic; producer compression preferred |
Topic Naming Convention
<domain>.<entity>.<event> → payments.orders.placed
<env>.<domain>.<entity> → prod.payments.orders
<team>.<service>.<event> → checkout.cart.item_added
dlq.<original-topic> → dlq.payments.orders.placed
Producer Configuration
Durability vs Throughput Trade-offs
# HIGH DURABILITY — production default for critical data
durable_producer = {
"bootstrap.servers": "broker1:9092,broker2:9092,broker3:9092",
"client.id": "orders-producer-v1",
# Durability
"acks": "all", # wait for all ISR acks
"enable.idempotence": True, # exactly-once per session
"max.in.flight.requests.per.connection": 5, # up to 5 with idempotence=True
# Throughput
"batch.size": 65536, # 64 KB batch size
"linger.ms": 10, # wait up to 10ms to fill batch
"compression.type": "snappy", # snappy: fast; gzip: best ratio; lz4: balanced
# Resilience
"retries": 2147483647, # retry indefinitely (with idempotence)
"delivery.timeout.ms": 120000, # give up after 2 min total
"request.timeout.ms": 30000,
"buffer.memory": 67108864, # 64 MB send buffer
}
# HIGH THROUGHPUT — telemetry / non-critical events
throughput_producer = {
"bootstrap.servers": "broker:9092",
"acks": "1", # leader ack only
"batch.size": 131072, # 128 KB
"linger.ms": 50,
"compression.type": "lz4",
"buffer.memory": 134217728, # 128 MB
}
Idempotent Producer (prevents duplicates on retry)
enable.idempotence=True assigns each producer a PID and a sequence number per partition. The broker deduplicates retried messages within the producer session.
Requires: acks=all, max.in.flight.requests.per.connection ≤ 5, retries > 0.
Transactional Producer (exactly-once across partitions)
from confluent_kafka import Producer
producer = Producer({
"bootstrap.servers": "broker:9092",
"transactional.id": "order-processor-1", # unique per producer instance
"enable.idempotence": True,
})
producer.init_transactions()
try:
producer.begin_transaction()
producer.produce("orders", key="1001", value='{"status":"placed"}')
producer.produce("audit", key="1001", value='{"action":"order_placed"}')
producer.commit_transaction()
except Exception as e:
producer.abort_transaction()
raise
Python Producer — Full Example
from confluent_kafka import Producer
from confluent_kafka.serialization import StringSerializer, SerializationContext, MessageField
import json, logging
logger = logging.getLogger(__name__)
def delivery_callback(err, msg):
if err:
logger.error(f"Delivery failed | topic={msg.topic()} partition={msg.partition()} error={err}")
else:
logger.debug(f"Delivered | topic={msg.topic()} partition={msg.partition()} offset={msg.offset()}")
producer = Producer({
"bootstrap.servers": "broker:9092",
"acks": "all",
"enable.idempotence": True,
"compression.type": "snappy",
"batch.size": 65536,
"linger.ms": 10,
})
def send_order(order: dict) -> None:
producer.produce(
topic="orders",
key=str(order["order_id"]).encode(),
value=json.dumps(order).encode(),
on_delivery=delivery_callback,
)
producer.poll(0) # trigger delivery callbacks without blocking
# After batch:
producer.flush() # block until all in-flight messages delivered
Consumer Configuration
Core Settings
consumer_config = {
"bootstrap.servers": "broker:9092",
"group.id": "orders-service-v1",
"client.id": "orders-consumer-1",
# Offset reset: what to do when no committed offset exists
"auto.offset.reset": "earliest", # earliest | latest | error
# ALWAYS disable auto-commit for production — control commits manually
"enable.auto.commit": False,
# Rebalance protocol
"partition.assignment.strategy": "cooperative-sticky", # minimises rebalance disruption
# Session & poll timeouts
"session.timeout.ms": 30000, # consumer considered dead after 30s without heartbeat
"heartbeat.interval.ms": 10000, # heartbeat every 10s (< session.timeout.ms / 3)
"max.poll.interval.ms": 300000, # max 5 min between poll() calls before consumer kicked
# Throughput
"fetch.min.bytes": 1024, # wait for at least 1 KB before returning
"fetch.max.wait.ms": 500,
"max.poll.records": 500, # max messages per poll()
}
Commit Strategies
from confluent_kafka import Consumer, KafkaError, TopicPartition
consumer = Consumer(consumer_config)
consumer.subscribe(["orders"])
# --- Strategy 1: Synchronous commit after each batch (safe, lower throughput)
try:
while True:
msgs = consumer.consume(num_messages=100, timeout=1.0)
for msg in msgs:
if msg.error():
logger.error(msg.error())
continue
process(msg)
if msgs:
consumer.commit(asynchronous=False) # block until broker confirms
except Exception:
pass
finally:
consumer.close()
# --- Strategy 2: Async commit in loop + sync commit on shutdown (balanced)
try:
while True:
msg = consumer.poll(1.0)
if msg is None:
continue
if msg.error():
logger.error(msg.error())
continue
process(msg)
consumer.commit(asynchronous=True)
except KeyboardInterrupt:
consumer.commit(asynchronous=False) # final sync flush
finally:
consumer.close()
# --- Strategy 3: Manual per-partition offset commit (exactly-once with external store)
def on_assign(consumer, partitions):
# restore offsets from external store (e.g. DB) on rebalance
for p in partitions:
offset = db.get_offset(p.topic, p.partition) or OFFSET_BEGINNING
p.offset = offset
consumer.assign(partitions)
consumer.subscribe(["orders"], on_assign=on_assign)
while True:
msg = consumer.poll(1.0)
if msg is None:
continue
process_and_save_to_db(msg) # write result + offset atomically
consumer.commit(offsets=[
TopicPartition(msg.topic(), msg.partition(), msg.offset() + 1)
], asynchronous=False)
Consumer Group Operations (CLI)
# List all consumer groups
kafka-consumer-groups.sh --bootstrap-server broker:9092 --list
# Describe group — shows lag per partition
kafka-consumer-groups.sh --bootstrap-server broker:9092 \
--describe --group orders-service-v1
# Reset offsets to beginning (for re-processing)
kafka-consumer-groups.sh --bootstrap-server broker:9092 \
--group orders-service-v1 --topic orders \
--reset-offsets --to-earliest --execute
# Shift offsets back by N messages
kafka-consumer-groups.sh --bootstrap-server broker:9092 \
--group orders-service-v1 --topic orders \
--reset-offsets --shift-by -1000 --execute
# Reset to specific datetime
kafka-consumer-groups.sh --bootstrap-server broker:9092 \
--group orders-service-v1 --topic orders \
--reset-offsets --to-datetime 2024-01-15T00:00:00.000 --execute
Exactly-Once Semantics (EOS)
| Delivery Guarantee | Producer | Consumer | Use When |
|---|---|---|---|
| At-most-once | acks=0/1, no retry | auto.commit before processing | Metrics, telemetry |
| At-least-once | acks=all + retries | commit after processing | Most pipelines (handle idempotent consumers) |
| Exactly-once | enable.idempotence=True + transactions | isolation.level=read_committed | Financial, inventory, audit |
# Exactly-once consumer: only read committed transactional messages
consumer = Consumer({
**consumer_config,
"isolation.level": "read_committed", # skip messages from aborted transactions
})
Schema Registry
Enforces schema evolution rules; decouples producers from consumers.
from confluent_kafka.schema_registry import SchemaRegistryClient
from confluent_kafka.schema_registry.avro import AvroSerializer, AvroDeserializer
from confluent_kafka.serialization import SerializationContext, MessageField
schema_registry_conf = {"url": "http://schema-registry:8081"}
schema_registry_client = SchemaRegistryClient(schema_registry_conf)
order_schema_str = """
{
"type": "record",
"name": "Order",
"namespace": "com.example",
"fields": [
{"name": "order_id", "type": "long"},
{"name": "amount", "type": "double"},
{"name": "status", "type": "string"}
]
}
"""
avro_serializer = AvroSerializer(schema_registry_client, order_schema_str)
avro_deserializer = AvroDeserializer(schema_registry_client)
# Producer with Avro serialization
producer = Producer({"bootstrap.servers": "broker:9092"})
producer.produce(
topic="orders",
value=avro_serializer(
{"order_id": 1001, "amount": 299.99, "status": "placed"},
SerializationContext("orders", MessageField.VALUE),
),
)
Schema compatibility modes (set per subject):
| Mode | Rule |
|---|---|
BACKWARD (default) | New schema reads old data (consumers upgrade first) |
FORWARD | Old schema reads new data (producers upgrade first) |
FULL | Both directions |
NONE | No checks — dangerous |
# Check compatibility before registering
curl -X POST http://schema-registry:8081/compatibility/subjects/orders-value/versions/latest \
-H "Content-Type: application/json" \
-d '{"schema": "<escaped_json_schema>"}'
Kafka Connect
Source Connector — PostgreSQL CDC (Debezium)
{
"name": "postgres-source",
"config": {
"connector.class": "io.debezium.connector.postgresql.PostgresConnector",
"database.hostname": "postgres",
"database.port": "5432",
"database.user": "debezium",
"database.password": "secret",
"database.dbname": "orders_db",
"topic.prefix": "cdc",
"table.include.list": "public.orders,public.customers",
"plugin.name": "pgoutput",
"slot.name": "debezium_slot",
"transforms": "route",
"transforms.route.type": "org.apache.kafka.connect.transforms.ReplaceField$Value",
"errors.tolerance": "all",
"errors.log.enable": "true",
"errors.log.include.messages": "true",
"errors.deadletterqueue.topic.name": "dlq.cdc.postgres-source",
"errors.deadletterqueue.topic.replication.factor": "3",
"errors.deadletterqueue.context.headers.enable": "true"
}
}
Sink Connector — Iceberg via Kafka Connect
{
"name": "iceberg-sink",
"config": {
"connector.class": "io.tabular.iceberg.connect.IcebergSinkConnector",
"tasks.max": "4",
"topics": "cdc.public.orders",
"iceberg.tables": "silver.orders",
"iceberg.catalog": "rest",
"iceberg.catalog.uri": "http://iceberg-rest:8181",
"iceberg.tables.upsert-mode-enabled": "true",
"iceberg.tables.evolve-schema-enabled": "true",
"errors.tolerance": "all",
"errors.deadletterqueue.topic.name": "dlq.iceberg-sink",
"errors.deadletterqueue.context.headers.enable": "true"
}
}
Dead Letter Queue — Reading Failed Messages
from confluent_kafka import Consumer
import json
dlq_consumer = Consumer({
"bootstrap.servers": "broker:9092",
"group.id": "dlq-inspector",
"auto.offset.reset": "earliest",
"enable.auto.commit": False,
})
dlq_consumer.subscribe(["dlq.cdc.postgres-source"])
while True:
msg = dlq_consumer.poll(1.0)
if msg is None:
continue
headers = {k: v.decode() for k, v in (msg.headers() or [])}
print({
"offset": msg.offset(),
"partition": msg.partition(),
"error": headers.get("__connect.errors.exception.message"),
"stage": headers.get("__connect.errors.stage"),
"connector": headers.get("__connect.errors.connector.name"),
"payload": msg.value(),
})
dlq_consumer.commit(asynchronous=False)
Connector REST API
# Deploy connector
curl -X POST http://connect:8083/connectors \
-H "Content-Type: application/json" \
-d @connector_config.json
# Check status
curl http://connect:8083/connectors/postgres-source/status
# Restart failed task
curl -X POST http://connect:8083/connectors/postgres-source/tasks/0/restart
# Update config without restart
curl -X PUT http://connect:8083/connectors/postgres-source/config \
-H "Content-Type: application/json" \
-d '{"table.include.list": "public.orders,public.customers,public.products", ...}'
# Delete connector
curl -X DELETE http://connect:8083/connectors/postgres-source
Monitoring — Consumer Lag
Via CLI
# Real-time lag across all partitions
watch -n 5 kafka-consumer-groups.sh \
--bootstrap-server broker:9092 \
--describe --group orders-service-v1
# Output columns: GROUP | TOPIC | PARTITION | CURRENT-OFFSET | LOG-END-OFFSET | LAG
Via Python (programmatic lag check)
from confluent_kafka.admin import AdminClient
from confluent_kafka import Consumer, TopicPartition
def get_consumer_lag(bootstrap_servers: str, group_id: str, topic: str) -> dict:
admin = AdminClient({"bootstrap.servers": bootstrap_servers})
consumer = Consumer({"bootstrap.servers": bootstrap_servers, "group.id": group_id})
metadata = admin.list_topics(topic=topic)
partitions = [
TopicPartition(topic, p)
for p in metadata.topics[topic].partitions
]
committed = consumer.committed(partitions)
high_watermarks = {
p.partition: consumer.get_watermark_offsets(p)[1] # (low, high)
for p in partitions
}
lag = {}
for tp in committed:
hw = high_watermarks[tp.partition]
committed_offset = tp.offset if tp.offset >= 0 else 0
lag[tp.partition] = hw - committed_offset
consumer.close()
return lag # {partition_id: lag}
# Alert if total lag > threshold
lag = get_consumer_lag("broker:9092", "orders-service-v1", "orders")
total_lag = sum(lag.values())
if total_lag > 10000:
alert(f"High consumer lag: {total_lag} messages behind")
Key JMX Metrics to Monitor
| Metric | Alert Threshold | Meaning |
|---|---|---|
kafka.consumer:records-lag-max | > 10 000 | Max lag across partitions |
kafka.producer:record-error-rate | > 0 | Producer delivery failures |
kafka.producer:request-latency-avg | > 100ms | Broker write latency |
kafka.server:UnderReplicatedPartitions | > 0 | Replicas falling behind |
kafka.server:ActiveControllerCount | ≠ 1 | Controller election issue |
kafka.server:BytesInPerSec | varies | Inbound throughput |
Topic Management CLI
# List topics
kafka-topics.sh --bootstrap-server broker:9092 --list
# Describe topic (partitions, replicas, ISR)
kafka-topics.sh --bootstrap-server broker:9092 --describe --topic orders
# Alter topic (add partitions — keyed topics lose ordering on existing keys!)
kafka-topics.sh --bootstrap-server broker:9092 --alter \
--topic orders --partitions 24
# Change retention
kafka-configs.sh --bootstrap-server broker:9092 \
--entity-type topics --entity-name orders \
--alter --add-config retention.ms=2592000000 # 30 days
# Delete topic
kafka-topics.sh --bootstrap-server broker:9092 --delete --topic orders
# Produce test messages
kafka-console-producer.sh --bootstrap-server broker:9092 \
--topic orders --property "key.separator=:" --property "parse.key=true"
# Consume from beginning
kafka-console-consumer.sh --bootstrap-server broker:9092 \
--topic orders --from-beginning \
--property print.key=true --property print.timestamp=true
Partition Strategy
from confluent_kafka import Producer
# Default: hash(key) % num_partitions — ensures order per key
producer.produce("orders", key=b"user_123", value=b"...")
# Round-robin: key=None
producer.produce("events", key=None, value=b"...")
# Custom partitioner (confluent-kafka via on_delivery is not partitioner)
# Use key design to control routing:
# - order_id → all events for one order go to same partition
# - user_id → all events for one user go to same partition
# - region → geographic affinity (but watch hotspots!)
# HOT PARTITION detection: one partition has 10x more messages
# Fix: use composite key = f"{high_cardinality_id}_{salt}"
# Or: use null key (round-robin) if ordering not needed
Docker Compose — Local Kafka Stack
version: "3.9"
services:
broker:
image: confluentinc/cp-kafka:7.6.0
hostname: broker
ports: ["9092:9092"]
environment:
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_LISTENERS: PLAINTEXT://broker:9092,CONTROLLER://broker:9093
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@broker:9093
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_DEFAULT_REPLICATION_FACTOR: 1
KAFKA_MIN_INSYNC_REPLICAS: 1
CLUSTER_ID: "MkU3OEVBNTcwNTJENDM2Qg" # base64 UUID
schema-registry:
image: confluentinc/cp-schema-registry:7.6.0
ports: ["8081:8081"]
environment:
SCHEMA_REGISTRY_HOST_NAME: schema-registry
SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: broker:9092
depends_on: [broker]
kafka-connect:
image: confluentinc/cp-kafka-connect:7.6.0
ports: ["8083:8083"]
environment:
CONNECT_BOOTSTRAP_SERVERS: broker:9092
CONNECT_GROUP_ID: kafka-connect-cluster
CONNECT_CONFIG_STORAGE_TOPIC: _connect-configs
CONNECT_OFFSET_STORAGE_TOPIC: _connect-offsets
CONNECT_STATUS_STORAGE_TOPIC: _connect-status
CONNECT_KEY_CONVERTER: org.apache.kafka.connect.json.JsonConverter
CONNECT_VALUE_CONVERTER: io.confluent.connect.avro.AvroConverter
CONNECT_VALUE_CONVERTER_SCHEMA_REGISTRY_URL: http://schema-registry:8081
CONNECT_REST_ADVERTISED_HOST_NAME: kafka-connect
depends_on: [broker, schema-registry]
kafka-ui:
image: provectuslabs/kafka-ui:latest
ports: ["8080:8080"]
environment:
KAFKA_CLUSTERS_0_NAME: local
KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: broker:9092
KAFKA_CLUSTERS_0_SCHEMAREGISTRY: http://schema-registry:8081
KAFKA_CLUSTERS_0_KAFKACONNECT_0_NAME: connect
KAFKA_CLUSTERS_0_KAFKACONNECT_0_ADDRESS: http://kafka-connect:8083
depends_on: [broker]
Best Practices
- Always set
acks=all+enable.idempotence=Truefor business-critical producers — prevents duplicates on broker failover. - Disable
enable.auto.commitin all production consumers — commit only after successful processing. - Use
partition.assignment.strategy=cooperative-sticky— incremental rebalancing eliminates stop-the-world pauses. - Design topics around consumers, not producers — partition count = desired consumer parallelism.
- Use keys for ordering guarantees — all messages with the same key go to the same partition and are processed in order.
- Never increase partitions on a keyed topic without a migration plan — key routing changes break ordering for existing keys.
- Always configure DLQ for Kafka Connect sinks —
errors.tolerance=allwithout DLQ silently drops data. - Monitor consumer lag as the primary health metric — lag > threshold triggers backpressure or scale-out.
- Use Schema Registry with BACKWARD compatibility — consumers can upgrade independently of producers.
- Set
min.insync.replicas = replication_factor - 1(e.g. 2 for RF=3) — ensures durability during one broker failure. - Separate log directories (
log.dirs) from OS disk — Kafka is I/O intensive; co-location degrades both. - Use
isolation.level=read_committedin consumers that read from transactional topics.
Anti-Patterns
| Anti-Pattern | Problem | Fix |
|---|---|---|
acks=0 or acks=1 for critical data | Data loss on broker failure | acks=all + enable.idempotence=True |
enable.auto.commit=True | Commits offset before processing complete → data loss on crash | Disable; commit manually after processing |
| Too many small topics (thousands) | Controller metadata overload, slow elections | Consolidate; use partitions for parallelism |
| 1 partition per topic | Zero consumer parallelism | Design for expected throughput |
max.in.flight.requests.per.connection > 5 with idempotence | Idempotence requires ≤ 5 | Leave default (5) or set to 1 for strict ordering |
Consuming in while True without close() | Partition never rebalanced to another consumer | Always consumer.close() in finally |
auto.offset.reset=latest in new consumer group | Misses all historical messages | Use earliest for new groups unless intentional |
| Storing large payloads (> 1 MB) in Kafka | Memory pressure, slow replication | Store in object storage, publish reference/URL |
Sharing group.id across different applications | One app steals partitions from another | Each application gets a unique group.id |
| Changing partition count on keyed topic in production | Key-to-partition mapping breaks, ordering violated | Add new topic version + consumer migration |
| No DLQ for Connect sinks | Errors silently drop data | Set errors.tolerance=all + errors.deadletterqueue.topic.name |