Skip to content
Search ESC

Kafka Producer and Consumer Best Practices: acks, Offsets, and Idempotence

2025-05-29 · Updated 2026-03-14 · 12 min read · Igor Bobriakov

Kafka producer and consumer tuning often comes down to a small set of decisions with outsized impact: acks, idempotence, retries, partitioning, offset commits, and error-handling strategy. Misconfigure those, and you get data loss, duplicates, or avoidable latency.

Apache Kafka excels at handling massive streams of data, but the reliability and efficiency of your entire streaming architecture heavily depend on how well your Producers publish data and how your Consumers process it. Misconfigured or poorly designed clients can lead to data loss, duplication, performance bottlenecks, and operational headaches.

At ActiveWizards, we specialize in building and optimizing robust Kafka ecosystems. This article dives into the core concepts of Kafka Producers and Consumers, highlighting best practices that ensure your data streaming is not just fast, but also dependable and resilient.

The Role of Kafka Producers: Sending Data Reliably

A Kafka Producer is a client application responsible for writing (publishing) records (events or messages) to Kafka topics. Its primary goal is to send data to Kafka brokers efficiently and with the desired level of durability.

Key Producer Concepts & Configurations:

  • Target Topic & Partitioning:

  • Producers send records to a specific topic.

    • If a record has a key, the producer usually hashes that key to select the target partition. This keeps all records for the same key in order within one partition.
    • If no key is provided, records are typically distributed round-robin across the available partitions.
    • You can implement a custom Partitioner when you need domain-specific routing logic.
  • Serialization:

    • Producers must serialize both the key and the value into byte arrays before sending records to Kafka.
    • Common serializers cover strings, integers, Avro, Protobuf, and JSON payloads.
    • A Schema Registry is usually the safest approach when you need schema evolution and stronger data contracts.
  • Acknowledgements (acks):

    • acks=0 (fire and forget): The producer does not wait for broker confirmation. Fastest, but messages can be lost if a broker fails immediately after receipt.
    • acks=1 (leader acknowledgement): The producer waits for the leader replica to confirm receipt. This balances latency and durability, but data can still be lost if the leader fails before followers replicate.
    • acks=all (or acks=-1): The producer waits for all in-sync replicas to acknowledge receipt. This offers the strongest durability, with higher latency.

Producer Acknowledgement (acks) Settings Compared:

acks ValueDescriptionDurabilityLatencyUse Case Example
acks=0Producer doesn’t wait for any broker acknowledgment.Lowest (potential data loss on broker failure).Lowest.Non-critical metrics, high-volume logging where some loss is acceptable.
acks=1 (Default)Producer waits for leader replica acknowledgment.Moderate (potential data loss if leader fails before ISRs replicate).Moderate.General use cases with a balance of performance and durability.
acks=all (or -1)Producer waits for all In-Sync Replicas (ISRs) to acknowledge.Highest (no data loss if at least one ISR remains).Highest.Critical data requiring strong durability guarantees (e.g., financial transactions).
  • Retries (retries & retry.backoff.ms):

    • Producers can automatically retry sends after transient failures such as network glitches or temporary leader unavailability.
    • Setting retries above 0 is a baseline reliability measure.
    • Be mindful of message reordering if max.in.flight.requests.per.connection is greater than 1 and idempotence is not enabled.
  • Idempotent Producer (enable.idempotence=true):

    • Idempotence prevents retries from writing duplicate messages to the Kafka log.
    • Kafka achieves this by assigning a Producer ID and sequence numbers to messages.
    • This should be the default for most production producers. It works best with acks=all and retries > 0.

Pro-Tip: Always enable idempotence (enable.idempotence=true) when using acks=all and retries > 0 for critical data. This combination prevents data loss and guards against message duplication caused by retries, providing robust delivery guarantees.

  • Batching (batch.size & linger.ms):

    • Producers collect records into batches before sending them to brokers, which improves throughput by reducing network overhead.
    • batch.size controls the maximum batch size in bytes.
    • linger.ms controls how long the producer waits to fill a batch before sending it anyway.
    • Larger batches and a slightly higher linger value usually improve throughput, but they can increase end-to-end latency.
  • Compression (compression.type):

    • Compressing batches with codecs such as gzip, snappy, lz4, or zstd reduces network and storage costs.
    • Compression happens on the producer side; consumers transparently decompress on read.

Quick Reference: Key Producer Configurations for Reliability

  • bootstrap.servers: List of Kafka broker addresses.
  • key.serializer / value.serializer: How keys and values are converted to bytes.
  • acks: Set to all for the strongest durability.
  • retries: Set above 0 and tune upward with idempotence enabled.
  • enable.idempotence: Set to true to avoid duplicate writes during retries.
  • max.in.flight.requests.per.connection: Keep at 1 if you are not using idempotence, or up to 5 with idempotence.
  • delivery.timeout.ms: Maximum total send time, including retries.
  • linger.ms / batch.size: Tune for throughput versus latency.
  • compression.type: For example gzip, snappy, lz4, or zstd.

Producer Best Practices for Reliability:

  • Set acks=all for critical data to ensure no data loss.
  • Enable Idempotence (enable.idempotence=true) to prevent duplicates during retries.
  • Configure adequate retries (e.g., 3 to 5, or Integer.MAX_VALUE with idempotence).
  • Use appropriate serializers and consider a Schema Registry for robust data contracts.
  • Choose keys wisely for desired ordering and even partition distribution. Avoid “hot keys.”
  • Tune batching and compression for a balance of throughput and latency suitable for your use case.
  • Handle send errors and callbacks gracefully in your producer application logic.
  • Monitor producer metrics: record send rate, error rate, batch size, compression ratio.

Illustrative Python Producer Configuration (Conceptual):

from kafka import KafkaProducer
import json
producer_config = {
'bootstrap_servers': ['localhost:9092'],
'value_serializer': lambda v: json.dumps(v).encode('utf-8'),
'key_serializer': lambda k: k.encode('utf-8'),
'acks': 'all',
'retries': 5,
'enable_idempotence': True, # Requires Kafka broker >= 0.11 and compatible client
'compression_type': 'gzip'
}
producer = KafkaProducer(**producer_config)
try:
# Example send:
topic_name = 'my_topic'
message_key = 'some_key'
message_value = {'data': 'example_payload', 'id': 123}
producer.send(topic_name, key=message_key, value=message_value)
producer.flush() # Block until all pending messages are sent
print(f"Message sent to {topic_name}")
# In a real application, you might have a loop or event-driven sends
except Exception as e:
# Log error
print(f"Producer error: {e}")
finally:
producer.close()

The Role of Kafka Consumers: Processing Data Effectively

A Kafka Consumer is a client application that subscribes to one or more topics and processes the records fetched from Kafka brokers.

Key Consumer Concepts & Configurations:

  • Consumer Groups (group.id):

  • Multiple consumer instances can belong to the same consumer group.

    • Kafka distributes topic partitions across consumers in the group, and each partition is consumed by exactly one group member at a time.
    • This enables horizontal scale and load balancing.
    • When consumers join or leave, Kafka rebalances partition ownership.
  • Deserialization:

    • Consumers must deserialize key and value bytes back into usable application objects.
    • The deserializer must match the producer-side serializer.
  • Offset Management & Committing:

    • Consumers track progress per partition using offsets.
    • Committing an offset records the latest successfully processed position so the consumer can resume after restarts.
    • enable.auto.commit=true (default): Offsets are committed automatically on a timer. This is convenient, but it can create duplicates or data loss if processing and commits drift out of sync.
    • enable.auto.commit=false: Your application decides when to commit by calling consumer.commitSync() or consumer.commitAsync(). This gives you tighter control over delivery guarantees.

Consumer Commit Strategies Compared:

StrategyMechanism & Key ConfigsPros & ConsTypical Use Case
Automatic Commitenable.auto.commit=true; offsets committed periodically via auto.commit.interval.ms.Pros: Simple to configure. Cons: Higher risk of data loss or duplicates if processing completes out of sync with commits.Non-critical data where occasional duplication or loss is acceptable.
Manual Commit (Sync)enable.auto.commit=false; application calls consumer.commitSync().Pros: Full control over commit timing and clearer at-least-once behavior. Cons: Blocking commit calls can hurt throughput if used too frequently.Critical processing where ordered commits and predictable recovery matter.
Manual Commit (Async)enable.auto.commit=false; application calls consumer.commitAsync().Pros: Better throughput because commit calls do not block. Cons: Failed async commits require careful callback handling and retry discipline.High-throughput workloads where the team can handle the added complexity.
  • Delivery Semantics (with Manual Commits):
    • At Most Once: Commit offsets before processing. If processing fails afterward, the message is lost.
    • At Least Once: Commit offsets after successful processing. If a crash happens before the commit, the message may be processed again after restart, so downstream logic should be idempotent.
    • Exactly Once (EOS): Requires transactional coordination or frameworks such as Kafka Streams that provide stronger built-in EOS guarantees.

Pro-Tip: For “at least once” processing with manual commits, always process your batch of records fully before committing their offsets. If your application crashes mid-batch after a commit, you might lose the unprocessed records from that batch.

  • Polling (consumer.poll()):

    • Consumers fetch records by calling poll() in a loop.
    • The poll timeout determines how long the consumer waits when no data is immediately available.
    • Heartbeats happen as part of the poll cycle. If poll() is not called frequently enough and max.poll.interval.ms is exceeded, Kafka will remove the consumer from the group and trigger a rebalance.
  • max.poll.records: The maximum number of records returned in one poll() call.

  • fetch.min.bytes and fetch.max.wait.ms: These settings tune how much data the broker returns and how long it waits, which affects throughput and latency.

  • auto.offset.reset: What to do when no initial offset exists or the current offset is invalid.

    • latest (default): Start from new messages.
    • earliest: Start from the beginning of the partition.
    • none: Throw an exception.

Quick Reference: Key Consumer Configurations for Reliability

  • bootstrap.servers: List of Kafka broker addresses.
  • group.id: Identifies the consumer group.
  • key.deserializer / value.deserializer: How keys and values are converted back into application objects.
  • enable.auto.commit: Set to false when you need controlled commits.
  • auto.offset.reset: Usually earliest or latest, depending on startup behavior.
  • max.poll.interval.ms: Maximum time between polls before Kafka removes the consumer from the group.
  • max.poll.records: Maximum number of records returned per poll.
  • fetch.min.bytes / fetch.max.wait.ms: Tune broker fetch behavior for throughput and latency.

Consumer Best Practices for Reliability:

  • Use Consumer Groups for scalability and fault tolerance.
  • Prefer Manual Offset Committing (enable.auto.commit=false) for better control, especially for “at least once” semantics.
  • Design for Idempotent Processing if “at least once” semantics can lead to duplicates your application cannot handle.
  • Handle Rebalances Gracefully: Implement ConsumerRebalanceListener to manage state or commit offsets.
  • Process records from poll() efficiently to avoid exceeding max.poll.interval.ms.
  • Commit offsets regularly but strategically; consider committing after processing a batch from poll.
  • Handle deserialization errors robustly (e.g., log and skip, or DLT).
  • Monitor consumer lag closely to detect processing bottlenecks and ensure timely consumption.

Illustrative Python Consumer Configuration (Conceptual - Manual Commit):

from kafka import KafkaConsumer
import json
consumer_config = {
'bootstrap_servers': ['localhost:9092'],
'group_id': 'my_processing_group',
'value_deserializer': lambda v: json.loads(v.decode('utf-8')),
'key_deserializer': lambda k: k.decode('utf-8'),
'auto_offset_reset': 'earliest',
'enable_auto_commit': False, # Manual commit
# 'max_poll_interval_ms': 300000 # Default is 5 minutes
}
consumer = KafkaConsumer('my_topic', **consumer_config) # Subscribing to 'my_topic'
try:
while True:
# Poll for new messages with a timeout (e.g., 1 second)
message_batch = consumer.poll(timeout_ms=1000)
if not message_batch:
continue # No messages, poll again
for topic_partition, messages in message_batch.items():
for message in messages:
print(f"Processing: Partition={message.partition}, Offset={message.offset}, Value={message.value}")
# --- Your message processing logic here ---
# Example: store_in_database(message.value)
# If processing is successful for this message or a sub-batch:
# (More robust strategies might commit less frequently or based on overall batch success)
pass # Placeholder for processing
# Commit offsets for this partition after processing its messages
# This commits up to the offset of the last message in this list for this partition
# In a real app, you might commit after processing a smaller batch or individual message
# if processing can fail per message.
# For simplicity here, committing after all messages from a partition in a poll are 'processed'.
if messages: # Ensure there are messages to get an offset from
offsets_to_commit = {topic_partition: messages[-1].offset + 1}
consumer.commit(offsets=offsets_to_commit)
print(f"Committed offset {messages[-1].offset + 1} for {topic_partition}")
except KeyboardInterrupt:
print("Consumer stopping...")
except Exception as e:
# Log error
print(f"Consumer error: {e}")
finally:
consumer.close()

Conclusion: Building Resilient Kafka Clients

Effectively configuring Kafka Producers and Consumers is paramount for building reliable, high-performance streaming data systems. By understanding their core mechanics and adhering to best practices around acknowledgements, idempotence, batching, offset management, and error handling, you can ensure data integrity and maximize the value of your Kafka deployment.

The configurations and strategies discussed here provide a strong foundation. However, every use case has unique requirements. Optimizing Kafka clients often involves careful tuning, benchmarking, and continuous monitoring in your specific environment.

Further Reading & Official Resources

For deeper dives into specific configurations and advanced topics, consult the official Apache Kafka documentation:

Need help hardening your Kafka producers and consumers?

We help engineering teams tune delivery guarantees, consumer lag behavior, partition strategy, and recovery workflows so Kafka stays reliable under production load.

Talk to the ActiveWizards team about Kafka architecture, troubleshooting, or modernization.

Production Deployment

Deploy this architecture

Submit system context, constraints, and delivery pressure. A Principal Engineer reviews every submission and recommends the right next step.

[ SUBMIT SPECS ]

No SDRs. A Principal Engineer reviews every submission.

About the author

Igor Bobriakov

AI Architect. Author of Production-Ready AI Agents. 15 years deploying production AI platforms and agentic systems for enterprise clients and deep-tech startups.