conduktor.io ↗

Maximize Kafka Consumer Read Throughput

Maximize consumer read throughput with large fetches and efficient rebalancing.

Recommended starting points curated by Conduktor. Always benchmark with your workload. Some broker configs are not available on managed services (AWS MSK, Confluent Cloud) — check your provider's documentation.

consumer

Config Change Why
Fetching
fetch.min.bytes
Kafka 0.9.0+
1 → 64KB Default of 1 byte causes the broker to respond to every FetchRequest immediately with whatever data is available, even a single record. Setting 64KB forces the broker to accumulate at least 64KB before responding, batching more records per network round-trip and reducing per-record overhead by 10-50x under moderate load.
• Adds up to fetch.max.wait.ms of latency when the topic is sparse or low-throughput; consumers appear to lag even when caught up.
fetch.max.bytescaution
Kafka 0.10.1+
50MB → 100MB Raising the per-FetchRequest cap from 50MB to 100MB allows the consumer to retrieve more data in a single network round-trip when many partitions are assigned. Critical when a single consumer handles 50+ partitions — the 50MB cap becomes a bottleneck shared across all partitions in one fetch cycle.
• The consumer must buffer up to 100MB per fetch before passing records to poll(); combined with max.poll.records this can spike heap usage by hundreds of MB. Must stay below broker's fetch.max.bytes.
max.partition.fetch.bytescaution
Kafka 0.9.0+
1MB → 4MB Increasing the per-partition fetch limit from 1MB to 4MB reduces the number of FetchRequests needed to drain a partition's backlog by 4x. Especially impactful for consumers with few partitions or when catching up after lag accumulation.
• Memory pressure: with N partitions assigned, peak buffer usage is N × 4MB. A consumer with 50 partitions could buffer 200MB before poll() returns. Must be ≤ fetch.max.bytes.
max.poll.recordscaution
Kafka 0.10.0+
500 → 2000 Raising max.poll.records from 500 to 2000 increases the batch size returned per poll() call, amortizing the per-poll overhead (offset tracking, deserializer invocation, application loop overhead) across 4x more records. Directly increases records/sec throughput in tight poll loops.
• Processing 2000 records per poll() takes longer; if processing time per record is non-trivial, you risk exceeding max.poll.interval.ms and triggering a rebalance. Profile processing time first.
max.poll.interval.mscaution
Kafka 0.10.1+
5min → 10min When processing large batches (max.poll.records=2000) with expensive operations (DB writes, external API calls), 5 minutes may be insufficient. Raising to 10 minutes prevents spurious rebalances triggered by slow batch processing, which reset progress and create rebalance storms under high load.
• If a consumer genuinely hangs or deadlocks, the group coordinator waits up to 10 minutes before reassigning its partitions, increasing maximum downtime per dead consumer.
Network & Buffers
receive.buffer.bytes
Kafka 0.9.0+
64KB → 1MB Raising the TCP receive buffer from 64KB to 1MB allows the OS to buffer large FetchResponses (fetching up to 100MB) without forcing the consumer to drain the socket mid-response. Reduces the number of recv() syscalls by up to 16x for large fetches, directly improving throughput.
• Each broker connection consumes 1MB of kernel socket buffer. With connections to 3 brokers, this adds only 3MB of kernel memory — negligible on modern hardware.
Partitioning
partition.assignment.strategycaution
Kafka 2.4+
class org.apache.kafka.clients.consumer.RangeAssignor,class org.apache.kafka.clients.consumer.CooperativeStickyAssignor → org.apache.kafka.clients.consumer.CooperativeStickyAssignor Switching from the default RangeAssignor (eager, stops all consumption during rebalance) to CooperativeStickyAssignor enables incremental rebalancing: only partitions that actually need to move are revoked, while the rest continue consuming. Eliminates the 'stop-the-world' rebalance pause that can last seconds to minutes on large consumer groups, directly improving sustained throughput.
• All consumers in the group must use the same assignor; a mixed group during rolling upgrade will fall back to eager rebalancing until all members are updated. Requires Kafka 2.4+.
Consumer Group
group.instance.idcaution
Kafka 2.3+
null → Setting a stable group.instance.id enables static group membership: when a consumer restarts, the broker recognizes it by ID instead of treating it as a new member, skipping the rebalance entirely during the session.timeout.ms window. Critical for high-throughput pipelines where rebalances cause seconds of total-group processing interruption.
• If the same instance.id is accidentally reused by two running consumers simultaneously, the second join will fence out the first (fencing is intentional per KIP-345 but can cause unexpected eviction). Each consumer instance must have a globally unique ID.