Implementing Retry Mechanisms
1. Understanding Retry Behavior
Kafka's retry behavior is managed through configuration properties and consumer/group management. When a consumer fails to process a message, it can retry the operation based on specific configurations.
| Property | Default Value | Description |
|---|---|---|
| max.poll.interval.ms | 300000 |
Maximum delay between subsequent calls to poll(). If the consumer takes longer than this, it is considered dead and will be removed from the group. |
| max.poll.records | 500 |
Maximum number of records returned in a single call to poll(). |
| retry.backoff.ms | 100 |
Backoff time in milliseconds between retries. |
| enable.auto.commit | true |
Automatically commit offsets after each poll(). |
| auto.commit.interval.ms | 5000 |
Time in milliseconds between automatic offset commits. |
Example: Configuring Retry Behavior
{"bootstrap.servers": "localhost:9092", "group.id": "my-group", "enable.auto.commit": true, "max.poll.interval.ms": 60000, "retry.backoff.ms": 100}
Note: The
max.poll.interval.ms setting is crucial for ensuring that consumers do not get kicked out of the consumer group due to long processing times. It should be set higher than the expected time for message processing.
Warning: Setting
enable.auto.commit to false requires manual offset management, which can lead to duplicate processing if not handled correctly.
2. Configuring Retry Count
| Property | Default Value | Description |
|---|---|---|
| retries | 3 |
Number of times to retry a failed operation before giving up |
| retry.backoff.ms | 1000 |
Initial backoff time in milliseconds between retries |
| retry.backoff.max.ms | 10000 |
Maximum backoff time in milliseconds between retries |
| retry.jitter | false |
Whether to add jitter to backoff times to prevent thundering herd |
Example: Producer Retry Configuration
{
"retries": 5,
"retry.backoff.ms": 2000,
"retry.backoff.max.ms": 30000,
"retry.jitter": true
}
Note: Increasing retry count can lead to increased load on the system and potential data duplication if not handled properly.
3. Setting Retry Backoff
| Property | Default Value | Description |
|---|---|---|
| retry.backoff.ms | 1000 |
Initial backoff time in milliseconds between retries |
| retry.backoff.max.ms | 10000 |
Maximum backoff time in milliseconds between retries |
| retry.jitter | false |
Whether to apply jitter to backoff times to prevent thundering herd |
Example: Configuring Retry Backoff
{
"retry.backoff.ms": 2000,
"retry.backoff.max.ms": 30000,
"retry.jitter": true
}
Note: Increasing
retry.backoff.ms helps reduce load on the broker during transient failures.
Warning: Setting
retry.backoff.max.ms too high may cause long delays in message processing.
4. Configuring Delivery Timeout
| Property | Default Value | Description |
|---|---|---|
| delivery.timeout.ms | 120000 |
Maximum time to await delivery confirmation before considering the message failed. |
| max.block.ms | 60000 |
Maximum time to block on send operations if the buffer is full. |
Note: The
delivery.timeout.ms setting should be higher than the max.block.ms to allow for retries and backoff strategies.5. Understanding Retriable Exceptions
| Exception Type | Description | Retriable? | Notes |
|---|---|---|---|
| org.apache.kafka.common.errors.RetriableException | Base class for exceptions that can be retried | true |
All subclasses are retriable by default |
| org.apache.kafka.common.errors.TimeoutException | Thrown when a Kafka operation times out | true |
Typically retriable, especially in producer/consumer contexts |
| org.apache.kafka.common.errors.RebalanceInProgressException | Thrown when a consumer is rebalancing | true |
Should be retried after rebalance completes |
| org.apache.kafka.common.errors.UnknownTopicOrPartitionException | Thrown when a topic or partition is unknown | true |
May be retried if the topic is created or partition is added |
| org.apache.kafka.common.errors.NotLeaderForPartitionException | Thrown when the broker is not the leader for a partition | true |
Should be retried after leader election |
| org.apache.kafka.common.errors.TopicAuthorizationException | Thrown when authorization fails for a topic | false |
Not retriable due to permission issues |
| org.apache.kafka.common.errors.InvalidTopicException | Thrown when a topic name is invalid | false |
Not retriable due to malformed topic name |
Note: Retriable exceptions are typically handled by Kafka's built-in retry mechanisms, but custom logic can be implemented to handle specific cases.
6. Handling Non-Retriable Errors
| Error Type | Description | Handling Strategy | Notes |
|---|---|---|---|
| SerializationError | Occurs when a message cannot be serialized or deserialized. | Log and discard; notify system administrators. | Usually indicates a schema mismatch or invalid data format. |
| TimeoutError | Producer or consumer times out during message processing. | Retry with exponential backoff; escalate if persistent. | May be due to network issues or overloaded brokers. |
| AuthorizationError | Producer or consumer lacks permissions to access topic or resource. | Fail fast; log and alert for configuration review. | Indicates misconfigured ACLs or credentials. |
| InvalidTopicError | Attempt to produce or consume from a non-existent topic. | Fail fast; do not retry unless topic is recreated. | Requires manual intervention to create or correct topic name. |
| FencedInstanceException | Consumer instance is fenced out due to rebalancing or concurrent consumer groups. | Stop consumer; restart with new session ID. | Indicates concurrent consumer group usage or stale session state. |
Example: Handling Serialization Errors
const { Kafka } = require('kafkajs');
const kafka = new Kafka({
clientId: 'my-app',
brokers: ['localhost:9092']
});
const producer = kafka.producer();
async function sendMessage() {
try {
await producer.connect();
await producer.send({
topic: 'my-topic',
messages: [
{ value: JSON.stringify({ invalid: '<object>' }) }
]
});
} catch (error) {
if (error.name === 'SerializationError') {
console.error('Failed to serialize message:', error);
// Log and discard
} else {
throw error;
}
}
}
Note: Non-retriable errors should not be retried, as they typically indicate configuration or data issues that require manual intervention.
7. Implementing Exponential Backoff
| Property | Value | Description |
|---|---|---|
| initialDelayMs | 1000 |
Initial delay before first retry attempt in milliseconds |
| maxDelayMs | 30000 |
Maximum delay between retries in milliseconds |
| multiplier | 2 |
Multiplier for exponential growth of delay |
| maxRetries | 5 |
Maximum number of retry attempts before giving up |
Example: Retry Configuration with Exponential Backoff
const retryConfig = {
initialDelayMs: 1000,
maxDelayMs: 30000,
multiplier: 2,
maxRetries: 5
};
function calculateDelay(retryCount, config) {
const delay = Math.min(config.initialDelayMs * Math.pow(config.multiplier, retryCount), config.maxDelayMs);
return delay;
}
// Example usage
for (let i = 0; i <= retryConfig.maxRetries; i++) {
const delay = calculateDelay(i, retryConfig);
console.log(`Retry ${i + 1} after ${delay}ms`);
}
Note: Exponential backoff helps prevent overwhelming the system with rapid retries and allows time for transient issues to resolve.
8. Using Circuit Breaker Pattern
The circuit breaker pattern is a design pattern used to detect failures and encapsulate the logic of preventing a failure from constantly recurring during a time window.
| Aspect | Description | Implementation |
|---|---|---|
| State | Tracks the health of a service or system | Open, Half-Open, Closed |
| Failure Threshold | Number of failures before opening the circuit | Configurable (e.g., 5) |
| Timeout | Duration before transitioning to Half-Open | Configurable (e.g., 60 seconds) |
Example: Circuit Breaker in Kafka Producer
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import java.util.Properties;
import java.util.concurrent.CompletableFuture;
public class CircuitBreakerKafkaProducer {
private final KafkaProducer<String, String> producer;
private volatile boolean isOpen = false;
private int failureCount = 0;
private long lastFailureTime = 0;
public CircuitBreakerKafkaProducer() {
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
this.producer = new KafkaProducer<>(props);
}
public CompletableFuture<org.apache.kafka.clients.producer.RecordMetadata> sendWithCircuitBreaker(String topic, String key, String value) {
if (isOpen && System.currentTimeMillis() - lastFailureTime < 60_000) {
return CompletableFuture.failedFuture(new RuntimeException("Circuit is open"));
}
return producer.send(new ProducerRecord<>(topic, key, value))
.thenApply(metadata -> {
failureCount = 0;
return metadata;
})
.exceptionally(throwable -> {
failureCount++;
lastFailureTime = System.currentTimeMillis();
if (failureCount >= 5) {
isOpen = true;
}
throw new RuntimeException("Failed to send message", throwable);
});
}
public void resetCircuit() {
isOpen = false;
failureCount = 0;
}
}
Note: The circuit breaker pattern helps in avoiding cascading failures and provides resilience to Kafka producers when downstream services are unavailable.
Summary of Circuit Breaker States
- Closed: Normal operation, requests are allowed to proceed.
- Open: Failures exceed threshold, all requests fail fast.
- Half-Open: After timeout, a few requests are allowed to test if the service is recovered.
9. Configuring Request Timeout
| Property | Default Value | Description |
|---|---|---|
| request.timeout.ms | 30000 |
Timeout for request to broker (in milliseconds). If the request does not complete within this time, it is considered failed. |
| max.in.flight.requests.per.connection | 5 |
Maximum number of unacknowledged requests the client will send on a single connection before blocking. |
Example: Setting Request Timeout
{
"bootstrap.servers": "localhost:9092",
"request.timeout.ms": 60000,
"max.in.flight.requests.per.connection": 1
}
10. Implementing Consumer Retry Logic
| Property | Description | Default Value | Notes |
|---|---|---|---|
| retry.backoff.ms | Backoff time between retries in milliseconds | 1000 |
Used in exponential backoff strategy |
| retry.max.attempts | Maximum number of retry attempts | 3 |
After this, the message is sent to a dead-letter topic |
| retry.topic.name | Name of the dead-letter topic | dlq-topic |
Used when retries are exhausted |
Example: Consumer Retry Configuration
const consumer = new KafkaConsumer({
brokers: ['localhost:9092'],
groupId: 'my-group',
retry: {
maxAttempts: 3,
backoffMs: 1000
},
deadLetterTopic: 'dlq-topic'
});
Note: The consumer must be configured to handle retries via
maxAttempts and backoffMs. When retries are exhausted, the message is sent to a dead-letter topic.