Kafka Development
This skill provides best practices for Apache Kafka event streaming and distributed messaging systems. Apply these guidelines when building Kafka-based applications.
Core Principles
- Kafka is a distributed event streaming platform for high-throughput, fault-tolerant messaging
- Unlike traditional pub/sub, Kafka uses a pull model - consumers pull messages from partitions
- Design for scalability, durability, and exactly-once semantics where needed
- Leave NO todos, placeholders, or missing pieces in the implementation
Workflow: Setting Up a Kafka Producer-Consumer Pipeline
- Define the topic — choose a descriptive name, set partition count based on expected consumer parallelism, and configure retention and replication factor.
- Design the message schema — register an Avro or JSON schema in Schema Registry; ensure backward compatibility from the start.
- Implement the producer — configure
acks=all, enable idempotence, select a partition key that distributes evenly, and add error handling with retry logic.
- Implement the consumer — set
enable.auto.commit=false, pick an appropriate auto.offset.reset policy, process messages idempotently, and commit offsets only after successful processing.
- Add observability — instrument producer send-rate, consumer lag, and broker under-replicated-partitions; propagate trace context in message headers.
- Test end-to-end — use Testcontainers or an embedded Kafka broker to verify the full produce-consume-commit cycle, including failure and rebalance scenarios.
- Deploy and monitor — roll out with lag alerts, dead-letter-topic routing for persistent failures, and dashboards for key broker and client metrics.
Architecture Overview
Core Components
- Topics: Categories/feeds for organizing messages
- Partitions: Ordered, immutable sequences within topics enabling parallelism
- Producers: Clients that publish messages to topics
- Consumers: Clients that read messages from topics
- Consumer Groups: Coordinate consumption across multiple consumers
- Brokers: Kafka servers that store data and serve clients
Key Concepts
- Messages are appended to partitions in order
- Each message has an offset - a unique sequential ID within the partition
- Consumers maintain their own cursor (offset) and can read streams repeatedly
- Partitions are distributed across brokers for scalability
Topic Design
Partitioning Strategy
- Use partition keys to place related events in the same partition
- Messages with the same key always go to the same partition
- This ensures ordering for related events
- Choose keys carefully - uneven distribution causes hot partitions
Partition Count
- More partitions = more parallelism but more overhead
- Consider: expected throughput, consumer count, broker resources
- Start with number of consumers you expect to run concurrently
- Partitions can be increased but not decreased
Topic Configuration
retention.ms: How long to keep messages (default 7 days)
retention.bytes: Maximum size per partition
cleanup.policy: delete (remove old) or compact (keep latest per key)
min.insync.replicas: Minimum replicas that must acknowledge
Producer Best Practices
Reliability Settings
acks=all # Wait for all replicas to acknowledge
retries=MAX_INT # Retry on transient failures
enable.idempotence=true # Prevent duplicate messages on retry
Performance Tuning
batch.size: Accumulate messages before sending (default 16KB)
linger.ms: Wait time for batching (0 = send immediately)
buffer.memory: Total memory for buffering unsent messages
compression.type: gzip, snappy, lz4, or zstd for bandwidth savings
Error Handling
- Implement retry logic with exponential backoff
- Handle retriable vs non-retriable exceptions differently
- Log and alert on send failures
- Consider dead letter topics for messages that fail repeatedly
Example: Java Producer with Idempotence and Error Handling
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.ACKS_CONFIG, "all");
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
props.put(ProducerConfig.RETRIES_CONFIG, Integer.MAX_VALUE);
props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");
try (KafkaProducer<String, String> producer = new KafkaProducer<>(props)) {
ProducerRecord<String, String> record =
new ProducerRecord<>("orders", "order-123", "{\"item\":\"widget\",\"qty\":5}");
producer.send(record, (metadata, exception) -> {
if (exception != null) {
log.error("Send failed for key=order-123", exception);
// Route to dead-letter topic or alert
} else {
log.info("Delivered to {}-{} offset {}",
metadata.topic(), metadata.partition(), metadata.offset());
}
});
}
Partitioner
- Default: hash of key determines partition (null key = round-robin)
- Custom partitioners for specific routing needs
- Ensure even distribution to avoid hot partitions
Consumer Best Practices
Offset Management
- Consumers track which messages they've processed via offsets
auto.offset.reset: earliest (start from beginning) or latest (only new messages)
- Commit offsets after successful processing, not before
- Use
enable.auto.commit=false for exactly-once semantics
Consumer Groups
- Consumers in a group share partitions (each partition to one consumer)
- More consumers than partitions = some consumers idle
- Group rebalancing occurs when consumers join/leave
- Use
group.instance.id for static membership to reduce rebalances
Processing Patterns
- Process messages in order within a partition
- Handle out-of-order messages across partitions if needed
- Implement idempotent processing for at-least-once delivery
- Consider transactional processing for exactly-once
Example: Java Consumer with Manual Offset Commit
Properties props = new Properties();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ConsumerConfig.GROUP_ID_CONFIG, "order-processing-group");
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props)) {
consumer.subscribe(Collections.singletonList("orders"));
while (running) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(500));
for (ConsumerRecord<String, String> record : records) {
try {
processOrder(record.key(), record.value());
} catch (Exception e) {
log.error("Failed to process offset={} key={}", record.offset(), record.key(), e);
publishToDeadLetterTopic(record, e);
}
}
consumer.commitSync(); // Commit only after successful processing
}
}
Timeouts and Failures
- Implement processing timeout to isolate slow events
- When timeout occurs, set event aside and continue to next message
- Maintain overall system performance over processing every single event
- Use dead letter queues for messages failing all retries
Error Handling and Retry
Retry Strategy
- Allow multiple runtime retries per processing attempt
- Example: 3 runtime retries per redrive, maximum 5 redrives = 15 total retries
- Runtime retries typically cover 99% of failures
- After exhausting retries, route to dead letter queue
Dead Letter Topics
- Create dedicated DLT for messages that can't be processed
- Include original topic, partition, offset, and error details
- Monitor DLT for patterns indicating systemic issues
- Implement manual or automated retry from DLT
Schema Management
Schema Registry
- Use Confluent Schema Registry for schema management
- Producers validate data against registered schemas during serialization
- Schema mismatches throw exceptions, preventing malformed data
- Provides common reference for producers and consumers
Schema Evolution
- Design schemas for forward and backward compatibility
- Add optional fields with defaults for backward compatibility
- Avoid removing or renaming fields
- Use schema versioning and migration strategies
Kafka Streams
State Management
- Implement log compaction to maintain latest version of each key
- Periodically purge old data from state stores
- Monitor state store size and access patterns
- Use appropriate storage backends for your scale
Windowing Operations
- Handle out-of-order events and skewed timestamps
- Use appropriate time extraction and watermarking techniques
- Configure grace periods for late-arriving data
- Choose window types based on use case (tumbling, hopping, sliding, session)
Security
Authentication
- Use SASL/SSL for client authentication
- Support SASL mechanisms: PLAIN, SCRAM, OAUTHBEARER, GSSAPI
- Enable SSL for encryption in transit
- Rotate credentials regularly
Authorization
- Use Kafka ACLs for fine-grained access control
- Grant minimum necessary permissions per principal
- Separate read/write permissions by topic
- Audit access patterns regularly
Monitoring and Observability
Key Metrics
- Producer: record-send-rate, record-error-rate, batch-size-avg
- Consumer: records-consumed-rate, records-lag, commit-latency
- Broker: under-replicated-partitions, request-latency, disk-usage
Lag Monitoring
- Consumer lag = last produced offset - last committed offset
- High lag indicates consumers can't keep up
- Alert on increasing lag trends
- Scale consumers or optimize processing
Distributed Tracing
- Propagate trace context in message headers
- Use OpenTelemetry for end-to-end tracing
- Correlate producer and consumer spans
- Track message journey through the pipeline
Testing
Unit Testing
- Mock Kafka clients for isolated testing
- Test serialization/deserialization logic
- Verify partitioning logic
- Test error handling paths
Integration Testing
- Use embedded Kafka or Testcontainers
- Test full producer-consumer flows
- Verify exactly-once semantics if used
- Test rebalancing scenarios
Performance Testing
- Load test with production-like message rates
- Test consumer throughput and lag behavior
- Verify broker resource usage under load
- Test failure and recovery scenarios
Common Patterns
Event Sourcing
- Store all state changes as immutable events
- Rebuild state by replaying events
- Use log compaction for snapshots
- Enable time-travel debugging
CQRS (Command Query Responsibility Segregation)
- Separate write (command) and read (query) models
- Use Kafka as the event store
- Build read-optimized projections from events
- Handle eventual consistency appropriately
Saga Pattern
- Coordinate distributed transactions across services
- Each service publishes events for next step
- Implement compensating transactions for rollback
- Use correlation IDs to track saga instances
Change Data Capture (CDC)
- Capture database changes as Kafka events
- Use Debezium or similar CDC tools
- Enable real-time data synchronization
- Build event-driven integrations
1---2name: kafka-development3description: Best practices for Apache Kafka event streaming and distributed messaging. Use when building event-driven architectures, implementing producer/consumer patterns, designing topic partitioning strategies, setting up Kafka Streams, configuring schema registries, or integrating change data capture pipelines.4---5
6# Kafka Development
7
8This skill provides best practices for Apache Kafka event streaming and distributed messaging systems. Apply these guidelines when building Kafka-based applications.
9
10## Core Principles
11
12- Kafka is a distributed event streaming platform for high-throughput, fault-tolerant messaging
13- Unlike traditional pub/sub, Kafka uses a pull model - consumers pull messages from partitions
14- Design for scalability, durability, and exactly-once semantics where needed
15- Leave NO todos, placeholders, or missing pieces in the implementation
16
17## Workflow: Setting Up a Kafka Producer-Consumer Pipeline
18
191. **Define the topic** — choose a descriptive name, set partition count based on expected consumer parallelism, and configure retention and replication factor.
202. **Design the message schema** — register an Avro or JSON schema in Schema Registry; ensure backward compatibility from the start.
213. **Implement the producer** — configure `acks=all`, enable idempotence, select a partition key that distributes evenly, and add error handling with retry logic.
224. **Implement the consumer** — set `enable.auto.commit=false`, pick an appropriate `auto.offset.reset` policy, process messages idempotently, and commit offsets only after successful processing.
235. **Add observability** — instrument producer send-rate, consumer lag, and broker under-replicated-partitions; propagate trace context in message headers.
246. **Test end-to-end** — use Testcontainers or an embedded Kafka broker to verify the full produce-consume-commit cycle, including failure and rebalance scenarios.
257. **Deploy and monitor** — roll out with lag alerts, dead-letter-topic routing for persistent failures, and dashboards for key broker and client metrics.
26
27## Architecture Overview
28
29### Core Components
30
31- **Topics**: Categories/feeds for organizing messages
32- **Partitions**: Ordered, immutable sequences within topics enabling parallelism
33- **Producers**: Clients that publish messages to topics
34- **Consumers**: Clients that read messages from topics
35- **Consumer Groups**: Coordinate consumption across multiple consumers
36- **Brokers**: Kafka servers that store data and serve clients
37
38### Key Concepts
39
40- Messages are appended to partitions in order
41- Each message has an offset - a unique sequential ID within the partition
42- Consumers maintain their own cursor (offset) and can read streams repeatedly
43- Partitions are distributed across brokers for scalability
44
45## Topic Design
46
47### Partitioning Strategy
48
49- Use partition keys to place related events in the same partition
50- Messages with the same key always go to the same partition
51- This ensures ordering for related events
52- Choose keys carefully - uneven distribution causes hot partitions
53
54### Partition Count
55
56- More partitions = more parallelism but more overhead
57- Consider: expected throughput, consumer count, broker resources
58- Start with number of consumers you expect to run concurrently
59- Partitions can be increased but not decreased
60
61### Topic Configuration
62
63- `retention.ms`: How long to keep messages (default 7 days)
64- `retention.bytes`: Maximum size per partition
65- `cleanup.policy`: delete (remove old) or compact (keep latest per key)
66- `min.insync.replicas`: Minimum replicas that must acknowledge
67
68## Producer Best Practices
69
70### Reliability Settings
71
72```
73acks=all # Wait for all replicas to acknowledge
74retries=MAX_INT # Retry on transient failures
75enable.idempotence=true # Prevent duplicate messages on retry
76```
77
78### Performance Tuning
79
80- `batch.size`: Accumulate messages before sending (default 16KB)
81- `linger.ms`: Wait time for batching (0 = send immediately)
82- `buffer.memory`: Total memory for buffering unsent messages
83- `compression.type`: gzip, snappy, lz4, or zstd for bandwidth savings
84
85### Error Handling
86
87- Implement retry logic with exponential backoff
88- Handle retriable vs non-retriable exceptions differently
89- Log and alert on send failures
90- Consider dead letter topics for messages that fail repeatedly
91
92### Example: Java Producer with Idempotence and Error Handling
93
94```java
95Properties props = new Properties();
96props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
97props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
98props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
99props.put(ProducerConfig.ACKS_CONFIG, "all");
100props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
101props.put(ProducerConfig.RETRIES_CONFIG, Integer.MAX_VALUE);
102props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");
103
104try (KafkaProducer<String, String> producer = new KafkaProducer<>(props)) {
105 ProducerRecord<String, String> record =
106 new ProducerRecord<>("orders", "order-123", "{\"item\":\"widget\",\"qty\":5}");
107
108 producer.send(record, (metadata, exception) -> {
109 if (exception != null) {
110 log.error("Send failed for key=order-123", exception);
111 // Route to dead-letter topic or alert
112 } else {
113 log.info("Delivered to {}-{} offset {}",
114 metadata.topic(), metadata.partition(), metadata.offset());
115 }
116 });
117}
118```
119
120### Partitioner
121
122- Default: hash of key determines partition (null key = round-robin)
123- Custom partitioners for specific routing needs
124- Ensure even distribution to avoid hot partitions
125
126## Consumer Best Practices
127
128### Offset Management
129
130- Consumers track which messages they've processed via offsets
131- `auto.offset.reset`: earliest (start from beginning) or latest (only new messages)
132- Commit offsets after successful processing, not before
133- Use `enable.auto.commit=false` for exactly-once semantics
134
135### Consumer Groups
136
137- Consumers in a group share partitions (each partition to one consumer)
138- More consumers than partitions = some consumers idle
139- Group rebalancing occurs when consumers join/leave
140- Use `group.instance.id` for static membership to reduce rebalances
141
142### Processing Patterns
143
144- Process messages in order within a partition
145- Handle out-of-order messages across partitions if needed
146- Implement idempotent processing for at-least-once delivery
147- Consider transactional processing for exactly-once
148
149### Example: Java Consumer with Manual Offset Commit
150
151```java
152Properties props = new Properties();
153props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
154props.put(ConsumerConfig.GROUP_ID_CONFIG, "order-processing-group");
155props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
156props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
157props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
158props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
159
160try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props)) {
161 consumer.subscribe(Collections.singletonList("orders"));
162
163 while (running) {
164 ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(500));
165 for (ConsumerRecord<String, String> record : records) {
166 try {
167 processOrder(record.key(), record.value());
168 } catch (Exception e) {
169 log.error("Failed to process offset={} key={}", record.offset(), record.key(), e);
170 publishToDeadLetterTopic(record, e);
171 }
172 }
173 consumer.commitSync(); // Commit only after successful processing
174 }
175}
176```
177
178### Timeouts and Failures
179
180- Implement processing timeout to isolate slow events
181- When timeout occurs, set event aside and continue to next message
182- Maintain overall system performance over processing every single event
183- Use dead letter queues for messages failing all retries
184
185## Error Handling and Retry
186
187### Retry Strategy
188
189- Allow multiple runtime retries per processing attempt
190- Example: 3 runtime retries per redrive, maximum 5 redrives = 15 total retries
191- Runtime retries typically cover 99% of failures
192- After exhausting retries, route to dead letter queue
193
194### Dead Letter Topics
195
196- Create dedicated DLT for messages that can't be processed
197- Include original topic, partition, offset, and error details
198- Monitor DLT for patterns indicating systemic issues
199- Implement manual or automated retry from DLT
200
201## Schema Management
202
203### Schema Registry
204
205- Use Confluent Schema Registry for schema management
206- Producers validate data against registered schemas during serialization
207- Schema mismatches throw exceptions, preventing malformed data
208- Provides common reference for producers and consumers
209
210### Schema Evolution
211
212- Design schemas for forward and backward compatibility
213- Add optional fields with defaults for backward compatibility
214- Avoid removing or renaming fields
215- Use schema versioning and migration strategies
216
217## Kafka Streams
218
219### State Management
220
221- Implement log compaction to maintain latest version of each key
222- Periodically purge old data from state stores
223- Monitor state store size and access patterns
224- Use appropriate storage backends for your scale
225
226### Windowing Operations
227
228- Handle out-of-order events and skewed timestamps
229- Use appropriate time extraction and watermarking techniques
230- Configure grace periods for late-arriving data
231- Choose window types based on use case (tumbling, hopping, sliding, session)
232
233## Security
234
235### Authentication
236
237- Use SASL/SSL for client authentication
238- Support SASL mechanisms: PLAIN, SCRAM, OAUTHBEARER, GSSAPI
239- Enable SSL for encryption in transit
240- Rotate credentials regularly
241
242### Authorization
243
244- Use Kafka ACLs for fine-grained access control
245- Grant minimum necessary permissions per principal
246- Separate read/write permissions by topic
247- Audit access patterns regularly
248
249## Monitoring and Observability
250
251### Key Metrics
252
253- **Producer**: record-send-rate, record-error-rate, batch-size-avg
254- **Consumer**: records-consumed-rate, records-lag, commit-latency
255- **Broker**: under-replicated-partitions, request-latency, disk-usage
256
257### Lag Monitoring
258
259- Consumer lag = last produced offset - last committed offset
260- High lag indicates consumers can't keep up
261- Alert on increasing lag trends
262- Scale consumers or optimize processing
263
264### Distributed Tracing
265
266- Propagate trace context in message headers
267- Use OpenTelemetry for end-to-end tracing
268- Correlate producer and consumer spans
269- Track message journey through the pipeline
270
271## Testing
272
273### Unit Testing
274
275- Mock Kafka clients for isolated testing
276- Test serialization/deserialization logic
277- Verify partitioning logic
278- Test error handling paths
279
280### Integration Testing
281
282- Use embedded Kafka or Testcontainers
283- Test full producer-consumer flows
284- Verify exactly-once semantics if used
285- Test rebalancing scenarios
286
287### Performance Testing
288
289- Load test with production-like message rates
290- Test consumer throughput and lag behavior
291- Verify broker resource usage under load
292- Test failure and recovery scenarios
293
294## Common Patterns
295
296### Event Sourcing
297
298- Store all state changes as immutable events
299- Rebuild state by replaying events
300- Use log compaction for snapshots
301- Enable time-travel debugging
302
303### CQRS (Command Query Responsibility Segregation)
304
305- Separate write (command) and read (query) models
306- Use Kafka as the event store
307- Build read-optimized projections from events
308- Handle eventual consistency appropriately
309
310### Saga Pattern
311
312- Coordinate distributed transactions across services
313- Each service publishes events for next step
314- Implement compensating transactions for rollback
315- Use correlation IDs to track saga instances
316
317### Change Data Capture (CDC)
318
319- Capture database changes as Kafka events
320- Use Debezium or similar CDC tools
321- Enable real-time data synchronization
322- Build event-driven integrations