KafkaIQ

6.1 – Error Handling, Retries & Dead Letter Queues


Overview

This module is the authoritative guide to error handling, retries, and Dead Letter Queues (DLQs) in Kafka Connect. By the end, you will understand:

  • Retry behavior for transient vs. permanent errors.
  • Error tolerance configurations (none, all).
  • Dead Letter Queue (DLQ) topics for failed records.
  • Skip vs. fail strategies.
  • Poison record handling.

All error handling concepts live here. Other modules will reference this when discussing specific connectors or reliability patterns, but will not re-explain these mechanics.


Error Types in Kafka Connect

1. Transient Errors

Definition: Temporary failures that may succeed on retry.

Examples:

  • Network timeouts (e.g., database connection lost, Kafka broker unreachable).
  • Database deadlocks or temporary locking issues.
  • External API rate limiting (429 Too Many Requests).

Handling: Retry with exponential backoff.

2. Permanent Errors

Definition: Failures that will not succeed on retry.

Examples:

  • Invalid data format (e.g., malformed JSON, schema mismatch).
  • Constraint violations (e.g., database unique key violation).
  • Authorization failures (e.g., invalid credentials).

Handling: Skip or send to Dead Letter Queue (DLQ).

3. Poison Records

Definition: Records that consistently cause errors, preventing the task from progressing.

Examples:

  • A single malformed JSON record in a stream of valid records.
  • A record that violates database constraints.

Handling: Skip or send to DLQ to unblock the pipeline.


Kafka Connect Error Handling Architecture

Default Behavior (Strict)

By default, Kafka Connect uses a fail-fast strategy:

  1. A task encounters an error.
  2. The task stops and transitions to FAILED state.
  3. The connector must be manually restarted to resume processing.

Use Case: Critical pipelines where data integrity is paramount.

Error Tolerance Mode

Kafka Connect supports an error tolerance mode that allows tasks to continue processing despite errors.

Configuration: errors.tolerance

Values:

  • none (default): Fail-fast (task stops on first error).
  • all: Continue processing, log errors, optionally send to DLQ.

Example:

{
  "errors.tolerance": "all"
}

Retry Configuration

1. Retry Timeout

Configuration: errors.retry.timeout

Purpose: Maximum time (in milliseconds) to retry a failed operation before giving up.

Default: 0 (no retries)

Example:

{
  "errors.retry.timeout": "300000"
}

Explanation: Retry for up to 5 minutes (300,000 ms) before marking the record as failed.

Best Practice: Set to 1-5 minutes for transient errors (e.g., network timeouts, temporary database unavailability).


2. Retry Delay

Configuration: errors.retry.delay.max.ms

Purpose: Maximum delay (in milliseconds) between retry attempts.

Default: 60000 (60 seconds)

Example:

{
  "errors.retry.delay.max.ms": "10000"
}

Explanation: Wait up to 10 seconds between retries (with exponential backoff).

Backoff Strategy: Connect uses exponential backoff starting from 1 second, doubling each retry until errors.retry.delay.max.ms is reached.

Example Backoff:

  • Retry 1: Wait 1 second
  • Retry 2: Wait 2 seconds
  • Retry 3: Wait 4 seconds
  • Retry 4: Wait 8 seconds
  • Retry 5+: Wait 10 seconds (capped at errors.retry.delay.max.ms)

Dead Letter Queue (DLQ)

What is a Dead Letter Queue?

A Dead Letter Queue (DLQ) is a Kafka topic where failed records are written for later inspection or reprocessing.

Purpose:

  • Unblock pipelines: Move poison records out of the main pipeline.
  • Audit failures: Inspect failed records to identify root causes.
  • Reprocess later: Manually or automatically retry failed records after fixing issues.

Enabling DLQ

Configuration: errors.deadletterqueue.topic.name

Example:

{
  "errors.tolerance": "all",
  "errors.deadletterqueue.topic.name": "connect-dlq",
  "errors.deadletterqueue.topic.replication.factor": "3"
}

Explanation:

  • Failed records are written to the connect-dlq topic.
  • The DLQ topic is created with replication factor 3 for durability.

DLQ Record Format

DLQ records include metadata about the failure:

Key: Original record key (preserved).

Value: Original record value (preserved).

Headers (added by Connect):

  • __connect.errors.topic: Original Kafka topic.
  • __connect.errors.partition: Original Kafka partition.
  • __connect.errors.offset: Original Kafka offset.
  • __connect.errors.connector.name: Connector name.
  • __connect.errors.task.id: Task ID.
  • __connect.errors.stage: Stage where error occurred (TRANSFORMATION, VALUE_CONVERTER, HEADER_CONVERTER).
  • __connect.errors.exception.class.name: Exception class (e.g., org.apache.kafka.connect.errors.DataException).
  • __connect.errors.exception.message: Error message.
  • __connect.errors.exception.stacktrace: Full stack trace (if enabled).

Example (Consume DLQ):

kafka-console-consumer --bootstrap-server localhost:9092 \
  --topic connect-dlq \
  --from-beginning \
  --property print.key=true \
  --property print.headers=true

DLQ Context (Detailed Metadata)

Configuration: errors.deadletterqueue.context.headers.enable

Purpose: Include additional context in DLQ headers (e.g., stack traces, original topic metadata).

Default: false

Example:

{
  "errors.deadletterqueue.context.headers.enable": "true"
}

Impact: Larger DLQ messages (due to stack traces), but easier debugging.


Logging Errors

Log All Errors

Configuration: errors.log.enable

Purpose: Log all errors to the Connect worker logs (in addition to DLQ).

Default: false

Example:

{
  "errors.log.enable": "true",
  "errors.log.include.messages": "true"
}

Explanation:

  • All errors are logged to connect.log.
  • errors.log.include.messages=true: Include failed record content in logs (use with caution for sensitive data).

Best Practice: Enable for debugging, but disable in production (logs can grow large).


Error Handling Strategies

Strategy 1: Fail-Fast (Default)

Configuration:

{
  "errors.tolerance": "none"
}

Behavior:

  • Task stops on first error.
  • Operator must manually restart the connector.

Use Case: Critical pipelines where data integrity is paramount (e.g., financial transactions).

Pros: No data loss, no silent failures.
Cons: Requires manual intervention, pipeline stops on transient errors.


Strategy 2: Log and Continue

Configuration:

{
  "errors.tolerance": "all",
  "errors.log.enable": "true",
  "errors.log.include.messages": "true"
}

Behavior:

  • Task continues processing despite errors.
  • Errors are logged, but records are not sent to DLQ (i.e., data is dropped).

Use Case: Non-critical pipelines where occasional data loss is acceptable (e.g., log ingestion).

Pros: Pipeline continues, no manual intervention.
Cons: Data loss, silent failures.


Configuration:

{
  "errors.tolerance": "all",
  "errors.deadletterqueue.topic.name": "connect-dlq",
  "errors.deadletterqueue.topic.replication.factor": "3",
  "errors.deadletterqueue.context.headers.enable": "true",
  "errors.log.enable": "true"
}

Behavior:

  • Task continues processing despite errors.
  • Failed records are written to DLQ.
  • Errors are logged.

Use Case: Production pipelines where data integrity is important, but downtime is unacceptable.

Pros: No pipeline downtime, failed records can be inspected and reprocessed.
Cons: Requires monitoring DLQ for failed records.


Strategy 4: Retry with DLQ Fallback

Configuration:

{
  "errors.tolerance": "all",
  "errors.retry.timeout": "300000",
  "errors.retry.delay.max.ms": "10000",
  "errors.deadletterqueue.topic.name": "connect-dlq",
  "errors.deadletterqueue.topic.replication.factor": "3"
}

Behavior:

  • Task retries transient errors for up to 5 minutes.
  • If retries fail, record is sent to DLQ.
  • Task continues processing.

Use Case: Pipelines with occasional transient errors (e.g., network timeouts, database connection issues).

Pros: Handles transient errors automatically, DLQ captures permanent failures.
Cons: Retries add latency for failed records.


Poison Record Handling

What is a Poison Record?

A poison record is a record that consistently causes errors, preventing the task from progressing (especially when errors.tolerance=none).

Example: A single malformed JSON record in a stream of valid records causes the connector to fail and stop processing all subsequent records.

Detection

Symptom: Connector repeatedly fails on the same offset.

Log Pattern:

Error processing record from topic 'orders' partition 2 offset 12345: 
org.apache.kafka.connect.errors.DataException: Failed to deserialize data

Mitigation Strategies

Option 1: Enable Error Tolerance + DLQ

Configuration:

{
  "errors.tolerance": "all",
  "errors.deadletterqueue.topic.name": "connect-dlq"
}

Result: Poison record is sent to DLQ, connector continues processing.

Option 2: Skip the Record Manually

If DLQ is not enabled, you can manually advance the consumer offset:

Steps:

  1. Pause the connector:
    curl -X PUT http://localhost:8083/connectors/my-connector/pause
    
  2. Manually commit offset past the poison record (requires custom script or Admin API).
  3. Resume the connector:
    curl -X PUT http://localhost:8083/connectors/my-connector/resume
    

Note: This is a last resort and should be automated via DLQ in production.

Option 3: Fix the Source Data

If the poison record originates from a source system, fix the data at the source and restart the connector.


Error Handling Best Practices

1. Always Use DLQ in Production

Rationale: Prevents pipeline downtime due to poison records.

Configuration:

{
  "errors.tolerance": "all",
  "errors.deadletterqueue.topic.name": "connect-dlq"
}

2. Monitor DLQ for Failed Records

Tools:

  • Kafka Consumer: Consume DLQ and alert on new messages.
  • Prometheus + Grafana: Monitor DLQ topic lag.
  • Confluent Control Center: View DLQ messages in UI.

Alerting Example (Prometheus):

- alert: ConnectDLQRecords
  expr: kafka_topic_partition_current_offset{topic="connect-dlq"} > 0
  annotations:
    summary: "Kafka Connect DLQ has failed records"

3. Enable Logging for Debugging

Configuration:

{
  "errors.log.enable": "true",
  "errors.log.include.messages": "true"
}

Note: Disable errors.log.include.messages in production if records contain sensitive data.

4. Use Retries for Transient Errors

Configuration:

{
  "errors.retry.timeout": "300000",
  "errors.retry.delay.max.ms": "10000"
}

Rationale: Automatically recovers from temporary failures (e.g., network issues, database restarts).

5. Test Error Scenarios

Test Cases:

  • Malformed JSON input (deserialization failure).
  • Database constraint violation (duplicate key).
  • Network timeout (transient error).
  • External system unavailability (retry behavior).

Testing Tool: Chaos Engineering tools (e.g., Chaos Monkey) to simulate failures.


Example: Full Error Handling Configuration

Scenario: Robust Production Sink Connector

Requirements:

  • Retry transient errors for up to 5 minutes.
  • Send permanent failures to DLQ.
  • Log all errors for debugging.

Configuration:

{
  "name": "elasticsearch-sink",
  "config": {
    "connector.class": "io.confluent.connect.elasticsearch.ElasticsearchSinkConnector",
    "tasks.max": "3",
    "topics": "logs",
    "connection.url": "http://localhost:9200",
    
    "errors.tolerance": "all",
    "errors.retry.timeout": "300000",
    "errors.retry.delay.max.ms": "10000",
    "errors.deadletterqueue.topic.name": "connect-dlq",
    "errors.deadletterqueue.topic.replication.factor": "3",
    "errors.deadletterqueue.context.headers.enable": "true",
    "errors.log.enable": "true"
  }
}

Behavior:

  1. Task encounters an error (e.g., Elasticsearch unavailable).
  2. Task retries for up to 5 minutes with exponential backoff.
  3. If error persists, record is sent to connect-dlq with full context.
  4. Error is logged to connect.log.
  5. Task continues processing subsequent records.

Common Error Scenarios

Scenario 1: Schema Mismatch

Error:

org.apache.kafka.connect.errors.DataException: Schema mismatch: expected field 'email', but was 'user_email'

Cause: Source data schema doesn't match target schema.

Solution:

  • Use SMTs to rename fields (ReplaceField).
  • Update target schema to match source.
  • Send to DLQ for manual inspection.

Scenario 2: Database Constraint Violation

Error:

org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "users_pkey"

Cause: Attempting to insert a record with a duplicate primary key.

Solution:

  • Use upsert mode (insert.mode=upsert) in JDBC Sink.
  • Send to DLQ and manually resolve conflicts.

Scenario 3: Network Timeout

Error:

java.net.SocketTimeoutException: connect timed out

Cause: External system (database, API) is unreachable.

Solution:

  • Enable retries (errors.retry.timeout=300000).
  • If retries fail, send to DLQ.
  • Monitor external system health.

Key Takeaways

  1. Error types: Transient (retry), permanent (skip/DLQ), poison records (unblock pipeline).
  2. errors.tolerance: none (fail-fast) vs. all (continue processing).
  3. Retries: Use errors.retry.timeout and errors.retry.delay.max.ms for transient errors.
  4. DLQ: Send failed records to errors.deadletterqueue.topic.name for inspection.
  5. Best practice: Enable DLQ + retries + logging in production.
  6. Monitoring: Alert on DLQ topic lag to detect failures.

What's Next?

  • Module 7 covers custom connector development, including custom error handling logic.
  • Module 8.1 explains monitoring and alerting for connector failures.
  • Module 5.1 discusses performance tuning (retries add latency).

Further Reading


End of Module 6.1