Skip to main content

Optimizing Consumer Performance

1. Tuning Fetch Size

Property Default Value Description Notes
fetch.min.bytes 1 Minimum amount of data the server should return for a fetch request Smaller values increase latency, larger values can cause delays in processing
fetch.max.bytes 52428800 Maximum amount of data the server should return for a fetch request Must be at least as large as max.partition.fetch.bytes
fetch.max.wait.ms 500 Maximum time the broker will wait for a fetch request to complete Higher values can increase latency but reduce CPU overhead

Example: Configuring Fetch Size

Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "consumer-group");
props.put("fetch.min.bytes", "1024");
props.put("fetch.max.bytes", "1048576");
props.put("fetch.max.wait.ms", "100");
props.put("enable.auto.commit", "true");
props.put("auto.offset.reset", "earliest");

KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);

2. Configuring Fetch Wait Time

Property Default Value Description
fetch.wait.max.ms 500 Maximum time in milliseconds the consumer will wait for new records when the fetch queue is empty.
fetch.min.bytes 1 Minimum number of bytes the consumer should fetch before returning records.
fetch.max.bytes 1048576 Maximum number of bytes the consumer should fetch in a single request.

Example: Configuring fetch.wait.max.ms

{"fetch.wait.max.ms": 1000}
Note: Increasing fetch.wait.max.ms can reduce CPU usage but may increase latency.

3. Setting Max Poll Records

Property Default Description
max.poll.records 500 Maximum number of records returned in a single call to poll().

Example: Configuring max.poll.records

{
  "max.poll.records": 1000
}
Note: Increasing max.poll.records can improve throughput but may increase latency due to longer processing times per poll cycle.

4. Optimizing Partition Assignment

Property Description Default Value Notes
partition.assignment.strategy Strategy used to assign partitions to consumers in a consumer group org.apache.kafka.clients.consumer.RangeAssignor Can be RangeAssignor, RoundRobinAssignor, or custom
max.partition.fetch.bytes Maximum number of bytes per partition the consumer will fetch in a single request 1048576 Increases memory usage; higher values can improve throughput
session.timeout.ms Timeout for consumer session to be considered alive 45000 Too low can cause unnecessary rebalances; too high delays failure detection
heartbeat.interval.ms Interval between heartbeats to the consumer group coordinator 3000 Must be less than session.timeout.ms

Example: Configuring Partition Assignment Strategy

Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "my-consumer-group");
props.put("partition.assignment.strategy", "org.apache.kafka.clients.consumer.RoundRobinAssignor");
props.put("enable.auto.commit", "true");
props.put("auto.commit.interval.ms", "1000");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
Note: The RoundRobinAssignor ensures a more even distribution of partitions across consumers in the group, especially when consumer lag is low.
Warning: Setting session.timeout.ms too low can cause frequent rebalances, leading to performance degradation and potential data loss.

5. Using Parallel Processing

Example: Consumer Group with Multiple Threads

import org.apache.kafka.clients.consumer.*;
import java.util.*;

public class ParallelConsumer {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("group.id", "parallel-group");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("enable.auto.commit", "false");

        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
        consumer.subscribe(Arrays.asList("my-topic"));

        // Process messages in parallel using multiple threads
        ExecutorService executor = Executors.newFixedThreadPool(4);
        for (int i = 0; i < 4; i++) {
            executor.submit(() -> {
                while (true) {
                    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));
                    for (ConsumerRecord<String, String> record : records) {
                        System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
                    }
                    consumer.commitSync();
                }
            });
        }
    }
}
Property Values Description
max.poll.records 500 Maximum number of records returned in a single poll
max.partition.fetch.bytes 1MB Maximum bytes of data per partition to fetch in a single poll
fetch.min.bytes 1KB Minimum bytes to fetch from each partition before returning records
Note: Using multiple consumer threads within a consumer group can improve throughput, but must be handled carefully to avoid rebalancing issues and ensure proper offset management.

Best Practices for Parallel Processing

  • Use max.poll.records to control batch size for better memory management
  • Adjust max.partition.fetch.bytes to optimize for large records
  • Ensure offset commits are synchronized across threads to prevent duplicate processing
  • Monitor consumer lag and adjust thread count based on workload

6. Configuring Prefetching

Property Default Value Description
fetch.min.bytes 1 Minimum number of bytes the consumer should fetch in a single request.
fetch.max.bytes 52428800 Maximum number of bytes the consumer should fetch in a single request.
fetch.max.wait.ms 500 Maximum time the consumer will wait for a fetch response.
max.partition.fetch.bytes 1048576 Maximum bytes the consumer should fetch per partition.

Example: Configuring Prefetching Parameters

{
  "fetch.min.bytes": 1024,
  "fetch.max.bytes": 10485760,
  "fetch.max.wait.ms": 1000,
  "max.partition.fetch.bytes": 2097152
}
Note: Adjusting fetch.min.bytes and fetch.max.bytes can help reduce the number of requests made to the broker, but setting these too high may lead to increased latency.

7. Optimizing Deserialization

Property Description Default Notes
key.deserializer Deserializer for message keys org.apache.kafka.common.serialization.StringDeserializer Must implement org.apache.kafka.common.serialization.Deserializer
value.deserializer Deserializer for message values org.apache.kafka.common.serialization.StringDeserializer Must implement org.apache.kafka.common.serialization.Deserializer
deserializer.byte.array Deserializer for byte arrays org.apache.kafka.common.serialization.ByteArrayDeserializer Useful for binary data or custom serialization logic

Example: Custom Deserializer

import org.apache.kafka.common.serialization.Deserializer;
import java.util.Map;

public class CustomObjectDeserializer implements Deserializer<MyObject> {
    @Override
    public void configure(Map<String, ?> configs, boolean isKey) {
        // Configuration logic if needed
    }

    @Override
    public MyObject deserialize(String topic, byte[] data) {
        if (data == null) return null;
        // Deserialize logic here
        return new MyObject(data);
    }

    @Override
    public void close() {
        // Cleanup logic if needed
    }
}
Note: Custom deserializers should be thread-safe and handle null values gracefully.
Warning: Incorrect deserializer configuration can lead to runtime exceptions or data corruption.
Deserializer Type Use Case Performance Impact Notes
StringDeserializer Text-based messages Low Fastest for simple string content
ByteArrayDeserializer Binary or custom serialized data Medium More flexible but requires manual handling
JsonDeserializer JSON-formatted messages High Slower due to parsing overhead but safer for structured data

8. Tuning Commit Frequency

Configuration Default Value Description
enable.auto.commit true Enables or disables automatic committing of offsets
auto.commit.interval.ms 5000 Interval in milliseconds to commit offsets when enable.auto.commit is true

Example: Configuring Auto Commit Interval

{
  "enable.auto.commit": true,
  "auto.commit.interval.ms": 1000
}
Note: Lower commit intervals increase consumer throughput but may lead to more duplicate processing if the consumer crashes between commits.

9. Using Asynchronous Commits

Asynchronous commits in Kafka consumers allow for better throughput by not blocking the consumer thread while waiting for commit acknowledgments. This is particularly useful in high-throughput scenarios where latency from synchronous commits can become a bottleneck.

Configuration Description Default Value Notes
enable.auto.commit Enables or disables automatic committing of offsets true When false, manual commits are required
auto.commit.interval.ms Interval in milliseconds to automatically commit offsets when enable.auto.commit is true 5000 Lower values increase commit frequency but may impact performance
enable.auto.commit Enables or disables automatic committing of offsets true When false, manual commits are required

Example: Asynchronous Commit Usage

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import java.util.Collections;
import java.util.Properties;

public class AsyncCommitExample {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(ConsumerConfig.GROUP_ID_CONFIG, "async-commit-group");
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); // Disable auto commit

        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
        consumer.subscribe(Collections.singletonList("test-topic"));

        while (true) {
            // Poll records
            consumer.poll(1000).forEach(record -> {
                System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
            });

            // Commit asynchronously
            consumer.commitAsync((offsets, exception) -> {
                if (exception != null) {
                    System.err.println("Commit failed for offsets " + offsets);
                } else {
                    System.out.println("Successfully committed offsets: " + offsets);
                }
            });
        }
    }
}
Note: Asynchronous commits do not guarantee that the offsets have been successfully committed before the consumer continues processing. It's important to handle potential commit failures in the callback.
Warning: If you use commitAsync without proper error handling, you may lose messages or consume duplicate messages in case of commit failures.

10. Monitoring Consumer Metrics

Metric Description Unit Notes
consumer.fetch.total Total number of fetch requests made by the consumer count Tracks how often the consumer polls for new messages
consumer.fetch.bytes.total Total bytes fetched by the consumer bytes Measures data throughput from Kafka brokers
consumer.commit.total Number of successful offset commits count Indicates how often offsets are committed to Kafka
consumer.commit.failed.total Number of failed offset commits count Signals potential issues with consumer group coordination
consumer.poll.timeout.total Number of times polling timed out count May indicate high load or slow processing
consumer.poll.latency.avg Average time spent in poll() calls ms High latency can impact throughput and responsiveness
consumer.records.lag.max Maximum lag in records between consumer and broker records Indicates how far behind the consumer is in processing messages
consumer.records.processed.total Total number of records processed by the consumer count Measures effective processing rate
consumer.records.failed.total Total number of records that failed processing count Indicates errors in message handling or processing logic

Example: Consumer Metrics Configuration

Properties props = new Properties();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ConsumerConfig.GROUP_ID_CONFIG, "my-group");
props.put(ConsumerConfig.METRICS_REPORTER_CLASSES_CONFIG, "org.apache.kafka.common.metrics.JmxReporter");
props.put(ConsumerConfig.METRICS_RECORDING_LEVEL_CONFIG, "DEBUG");
Note: Metrics are exposed via JMX and can be monitored using tools like Prometheus or Grafana.

Was this article helpful?