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. |
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(). |
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.recordsto control batch size for better memory management - Adjust
max.partition.fetch.bytesto 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.