| name | kafka-patterns |
| description | Topic design, partition strategies, consumer group patterns, exactly-once processing, and dead letter queue handling. |
Kafka Patterns
Event streaming patterns for Apache Kafka in distributed systems.
Topic Design
topics:
orders.order.created:
partitions: 12
replication-factor: 3
retention.ms: 604800000
cleanup.policy: delete
orders.order.changelog:
partitions: 12
replication-factor: 3
retention.ms: -1
cleanup.policy: compact
min.compaction.lag.ms: 3600000
Producer Patterns
import { Kafka, Partitioners, CompressionTypes } from 'kafkajs'
const kafka = new Kafka({
clientId: 'order-service',
brokers: process.env.KAFKA_BROKERS!.split(','),
})
const producer = kafka.producer({
idempotent: true,
maxInFlightRequests: 5,
createPartitioner: Partitioners.DefaultPartitioner,
})
await producer.connect()
async function publishOrderEvent(order: Order, eventType: string): Promise<void> {
await producer.send({
topic: `orders.order.${eventType}`,
compression: CompressionTypes.LZ4,
messages: [{
key: order.,
: .({
: crypto.(),
eventType,
: ().(),
: order,
}),
: {
: ,
: ,
: order.,
},
}],
})
}
(): <> {
producer.({
: [{
: ,
: events.( ({
: e.,
: .(e),
})),
}],
})
}
Consumer Group Patterns
const consumer = kafka.consumer({
groupId: 'payment-processor',
sessionTimeout: 30000,
heartbeatInterval: 3000,
maxBytesPerPartition: 1048576,
retry: { retries: 5 },
})
await consumer.connect()
await consumer.subscribe({
topics: ['orders.order.created'],
fromBeginning: false,
})
await consumer.run({
autoCommit: false,
eachBatchAutoResolve: false,
eachBatch: async ({ batch, resolveOffset, commitOffsetsIfNecessary, heartbeat }) => {
for (const message of batch.messages) {
try {
const event = JSON.parse(message.value!.toString())
if (await isAlreadyProcessed(event.eventId)) {
(message.)
}
(event.)
(event.)
(message.)
()
()
} (err) {
.(, err)
(message, err )
(message.)
}
}
},
})
Dead Letter Queue (DLQ)
const DLQ_TOPIC = 'orders.order.created.dlq'
async function sendToDeadLetterQueue(
originalMessage: KafkaMessage,
error: Error
): Promise<void> {
await producer.send({
topic: DLQ_TOPIC,
messages: [{
key: originalMessage.key,
value: originalMessage.value,
headers: {
...originalMessage.headers,
'dlq-reason': error.message,
'dlq-timestamp': new Date().toISOString(),
'dlq-original-topic': 'orders.order.created',
'dlq-retry-count': '0',
},
}],
})
}
async function processDLQ(): Promise<void> {
const dlqConsumer = kafka.consumer({ groupId: 'dlq-processor' })
await dlqConsumer.subscribe({ topics: [DLQ_TOPIC] })
dlqConsumer.({
: ({ message }) => {
retryCount = (
message.?.[]?.() ??
)
(retryCount >= ) {
({
: ,
: message.?.(),
: message.?.[]?.(),
: retryCount,
})
}
{
event = .(message.!.())
(event.)
} (err) {
producer.({
: ,
: [{
: message.,
: message.,
: {
...message.,
: (retryCount + ),
},
}],
})
}
},
})
}
Partition Strategy
const regionalPartitioner = () => ({
partition: ({ topic, partitionMetadata, message }) => {
const region = message.headers?.['region']?.toString() ?? 'default'
const regionMap: Record<string, number> = {
'us-east': 0, 'us-west': 1,
'eu-west': 2, 'eu-east': 3,
'ap-southeast': 4,
}
const partition = regionMap[region]
if (partition !== undefined && partition < partitionMetadata.length) {
return partition
}
const numPartitions = partitionMetadata.length
const hash = murmurHash(message.key?.toString() ?? '')
return Math.abs(hash) % numPartitions
}
})
Checklist
Anti-Patterns
- Auto-commit offsets: message loss if consumer crashes before processing
- Single partition topics: no parallelism, bottleneck
- Unbounded retry: infinite retry loop blocks partition processing
- Large messages (>1MB): use claim-check pattern (store in S3, send reference)
- Skipping idempotency: duplicate processing on consumer restart
- Global ordering requirement: use single partition only when truly needed