期間限定オファー:Pro / Ultraプラン初月が50%OFF🎉

Kafka Producer Performance Testing: A Practical Guide to Tuning and Diagnosis

Aug 18, 2026

Apache Kafka has become the backbone of high-throughput, low-latency data pipelines, and the producer is where much of that performance is won or lost. A well-tuned producer can push tens or hundreds of thousands of messages per second with predictable latency; a misconfigured one can choke on a fraction of that load and turn a streaming architecture into a series of timeouts and retries. This guide is a hands-on primer to measuring and improving producer performance, from understanding the architecture to tuning the parameters that matter and diagnosing real bottlenecks.

You do not need to be a Kafka expert to get value here, but you should have a working topic and a producer you can run against in a test environment. Everything below is written to be applied, not just read.

How the producer actually works

To tune something you first have to understand what it does between your application and the brokers. The producer is not a simple synchronous sender; it is a buffered, asynchronous pipeline with several moving parts.

The flow looks like this:

  1. Your application calls send on a record.
  2. The record is serialized (key and value) and handed to a partitioner that decides which partition it belongs to.
  3. The record lands in an accumulator, which groups records into batches per topic-partition.
  4. A background sender thread picks ready batches, attaches request metadata, and sends them to the brokers over TCP.
  5. The broker acknowledges, and the producer tracks delivery through callbacks or the returned future.

Understanding this pipeline matters because it explains why the "obvious" fixes are often wrong. Reconfiguring the network or the broker will not help if your real problem is a tiny batch size or a saturated buffer memory. Most producer performance issues are buffering issues in disguise.

The KPIs that actually matter

Before you change anything, decide what you are optimizing for. Producer performance is multidimensional, and there is no single number that captures it. Track these metrics together:

  • Throughput: messages or bytes per second. This is the headline number most teams care about first.
  • Latency (p50, p99, max): the time from send to the broker acknowledgment. Watch the tail; p99 tells you far more about real user pain than the average.
  • Error and retry rate: retries eat throughput and signal a misconfigured durability or batch setup.
  • Buffer memory utilization: how full your record accumulator is. Perpetually near-full means you are backpressuring the application and likely dropping or blocking sends.
  • Compression ratio: how effective your compression is, which interacts with both network use and CPU.

None of these alone is the answer. A producer with great throughput but terrible p99 latency and a pile of retries is not fast; it is fragile. Decide the SLO you are targeting first, then tune toward it.

Building a repeatable load test

You cannot tune what you cannot measure, and one-off ad-hoc tests produce misleading numbers. Set up a test harness that is clean, reproducible, and isolated from production noise.

The essentials:

  • Test broker(s): run a dedicated broker or cluster so your test results are not distorted by unrelated production traffic.
  • Synthetic load driver: a program that sends a controlled volume of records with a fixed shape (size, key cardinality, value size) so results compare cleanly across runs.
  • Baseline config: record the broker version, producer version, network MTU, and machine specs before every run.
  • Repeatable scenario: vary one input at a time (batch size, acks, volume) while holding everything else fixed, and run each scenario several times to average out noise.
  • Instrumentation: export producer metrics to a monitoring tool so you can inspect counters rather than guessing from throughput alone.

A common trap is testing against a remote, shared cluster and blaming the producer for what is really a shared-network problem. If you can, run your first tests against a local broker so you isolate the producer's own behavior. This makes the difference between confusion and diagnosis.

Tuning batching and latency

The single biggest lever on producer performance is batching, and it is controlled mostly by two settings: the linger time and the batch size.

Batches amortize the fixed cost of a network request. If you send one record per request, you pay protocol overhead and round-trip latency for every record, which crushes throughput. Larger batches spread that cost across many records, but they also introduce delivery delay because the producer waits to collect enough records.

Practical guidance:

  • Raises linger.ms if latency is your friend: a small linger (a few milliseconds) lets the sender coalesce bursts without a huge delay. Starting near 5ms is a sensible baseline for many workloads.
  • Raise batch.size (larger than the single largest record) when you have sustained high volume and want fewer, bigger requests. If batch.size is too small, the producer cannot fit the records, which forces effectively immediate sends, the opposite of batching.
  • Measure the trade-off: plot throughput and latency against linger values. The knee of that curve tells you your default.

The balance is workload-specific. A chat-message pipeline wants low latency and small batches; a log-aggregation pipeline wants maximum throughput and can tolerate much larger, slower batches.

Where durability and throughput collide

The acknowledged setting is where you spend durability to buy speed, or spend speed to buy durability. There is no free lunch, and the right choice depends entirely on how much data loss you can tolerate.

  • acks = all waits for all in-sync replicas to acknowledge. Highest durability, highest latency, and the only mode that fully protects against broker failure at write time.
  • acks = 1 acknowledges after the leader accepts the record. Lower latency, but if the leader fails before followers replicate, the record is lost.
  • acks = 0 fires and forgets. Fastest, but even leader-side confirmation is skipped; you may silently drop records.

For most pipelines that matter, acks = all is the right default, and you should treat lower settings as a deliberate, reviewed trade-off rather than a default. Pair it with the retry settings so transient broker hiccups do not cascade into errors. When you need more throughput, reach for better batching and compression before you start weakening durability.

Memory, buffers, and request sizing

The producer buffers unsent records in memory, and this buffer is a finite, tunable resource. Two numbers dominate this story: the total buffer memory and what happens when the buffer is full.

If the accumulator fills up, the producer has two choices when sends outpace the network: block the application (via max.block.ms) or start failing. Neither is great, but you want to stay far away from the cliff either way.

Settings that matter here:

  • Buffer memory: the cap on outstanding, unsent bytes. Too small and you block or backpressure under load; too large and you risk memory pressure and unbounded latency for a record stuck behind a wall of queued data.
  • max.block.ms: how long send blocks when the buffer is full. If this fires often, your producer is overloaded and the fix is upstream, not a longer timeout.
  • Compression (lz4, zstd, snappy): compression shrinks bytes on the wire and interacts with the network and broker storage. zstd gives great ratios at a CPU cost; lz4 is lighter.

Instrument these together: watch buffer utilization and error rates side by side. A healthy producer runs at a comfortable but not saturated buffer level, with negligible block time.

Finding and fixing latency bottlenecks

When latency is bad, do not guess; isolate the stages. A structured approach separates the culprits:

  • Serialization and partitioning: if you serialize in the application, profile it. Slow serialization or a partitioner that does heavy work (like deterministic key hashing with many partitions) adds latency before the record even enters the buffer.
  • Network and broker round-trip: measure broker-side request time separately from client-observed latency. If broker time is low but client latency is high, the gap is in buffering, batch-coalescing, or retry behavior.
  • Retries masking problems: a high retry rate is a symptom. Find the cause (flaky connection, timeouts too short, brokers unavailable) rather than just extending the retry window.
  • Tail latency: if p99 spikes but p50 is fine, look at GC pauses, uneven partition load, or a few slow brokers dragging the whole cluster.

Choose which stage to attack based on evidence, not intuition. Measure each hop, note the time budget you need per hop, and keep iterating until the distribution, not just the average, meets your target.

A practical tuning checklist

Putting it all together, here is a sequence that works when you inherit an unknown producer and need to improve it:

  1. Establish a baseline with your synthetic load test before changing anything.
  2. Set correct batching (linger and batch size) for your latency-througgphut goal.
  3. Confirm response settings match your durability requirement; prefer the strongest you can afford.
  4. Size the buffer memory above your peak in-flight byte requirement and confirm block time stays near zero.
  5. Add compression and confirm the CPU cost is acceptable.
  6. Re-run the baseline and compare throughput and p99 latency side by side.
  7. If latency is still poor, instrument and isolate serialization, network, and broker stages.
  8. Document the settings and the SLO they were tuned for so the next person does not start from zero.

A sample baseline configuration to start from

When you are tuning a new producer and have no prior art to borrow from, start with a defensible baseline rather than defaults and measure from there. The following sketch is a reasonable starting point for a log-aggregation workload that wants high throughput with acks = all:

  • linger set to a small nonzero value so bursts coalesce.
  • batch large enough to hold the largest records plus headroom.
  • acks = all with a sane retry count and retry backoff so transient broker issues do not become permanent failures.
  • compression enabled for the network-heavy case.
  • buffer memory sized above the expected peak in-flight bytes.
  • an idempotent producer enabled so duplicates from unavoidable retries are handled deterministically.

Run your synthetic load test with this baseline, record the numbers, then change exactly one setting at a time and re-measure. This disciplined approach produces a clear cause-and-effect table instead of folklore about what "feels" fast. What matters is not that you copy this configuration verbatim, but that you treat configuration as a hypothesis to be tested against your own workload, traffic shape, and durability requirements.

What happens under backpressure

A producer that is pushed past its limits does not just slow down; it changes behavior in ways that are easy to misread. When the record accumulator fills because the network or brokers cannot keep up, the producer will eventually block the calling thread rather than accept unbounded data. That blocking propagates backward into your application, turning a streaming pipeline into a stalled one, and it can masquerade as an application bug when it is actually a producer-buffer problem.

Learn to recognize the symptoms. A rising block time, frequent max-block errors, and a buffer utilization pinned near the ceiling all point to the same root cause: producers attempting to ingest more than the pipeline can drain. The instinct to raise the timeout only delays the inevitable. The real fixes are upstream or in the drain path: reduce per-record overhead, add compression, add more partitions or brokers to increase parallelism, or push back on the source application to apply its own throttling.

This is why the producer should never be tuned in isolation. It sits in a chain that includes your application, the network, the brokers, and the consumers that drain the topics. A producer that looks slow may simply be reflectively honest about a slow underlying pipeline, and a producer that looks fast may be hiding latency in the buffer. Measure the whole path, not just the send call.

Frequently asked questions

Should I increase linger.ms to improve throughput?
Only if your workload tolerates the added delivery delay. Raise it gradually and watch both throughput and p99 latency; the sweet spot is where throughput stops climbing meaningfully.

Is acks = 1 acceptable for most cases?
It is faster but risks losing records if the leader fails before replication. Choose it only when you can accept that risk and have a mitigation, such as an idempotent producer and retries.

Why does my p99 latency stay high even at low throughput?
Look for periodic events: GC pauses, a slow broker, or batching that occasionally waits a long time for a rare partition to fill. These tend to hit the tail, not the average.

Does a bigger buffer memory always help?
No. It raises memory use and can let latency balloon for records stuck behind a large queue. Size it to your peak in-flight bytes and no more.

What is the fastest win?

Disabling per-record request overhead with sensible batching, plus enabling compression, usually moves the needle more than any other single change.

Producer tuning is not a one-time activity; it is a loop you re-run as your traffic shape and durability needs evolve. A repeatable test harness and clear SLOs turn that loop from guesswork into a discipline.

Alexander

Alexander