Producer Tuning, Compression, and Capacity Planning
Producer throughput and broker load are shaped by three orthogonal knobs — batching (linger.ms, batch.size), compression codec, and partition count. Getting any one wrong forces a costly live repartition or compression migration under traffic. This article provides the engineering model for each decision, including the partition count sizing formula and the zstd vs snappy vs lz4 trade-off matrix.
Distributed Messaging Systems
Producer Tuning, Compression, and Capacity Planning
The order event pipeline was working fine at 5,000 messages per second. At 50,000 per second, the Kafka brokers are saturated — disk I/O at 95%, network egress pegged. The team adds more brokers and partitions. The saturation persists. The actual problem is that each producer is sending one message per network round trip with no batching (linger.ms=0, the default), generating 50,000 individual write requests per second to the broker. Each request incurs TCP framing, broker log append, ISR synchronisation overhead, and ack serialisation — multiplied by 50,000. With linger.ms=5 and batch.size=65536, the same 50,000 messages are grouped into ~800 batched requests per second. The broker I/O drops 60×.
Producer tuning is not premature optimisation. The three knobs — batching, compression, and partition count — are architectural decisions made at the start of a system's lifecycle. Changing them under live traffic is costly and risky.
Series positioning: This is Part 11 of Distributed Messaging Systems. It pairs with Part 6 (Consumer Patterns) to complete the full producer-to-consumer pipeline performance picture. The partition count sizing model builds on Scaling: Partitioning, Sharding, and Replication.
1. Batching: linger.ms and batch.size
1.1 How Kafka Batching Works
Kafka's producer accumulates messages into a RecordBatch before sending. Two thresholds control when a batch is flushed:
batch.size(default: 16,384 bytes = 16 KB): flush when the batch reaches this sizelinger.ms(default: 0): flush after this many milliseconds even if batch is not full- The batch sends when either threshold is crossed — whichever comes first
1.2 The Batching Trade-off
linger.ms |
batch.size |
Latency | Throughput | Best for |
|---|---|---|---|---|
0 |
16 KB |
Lowest (immediate send) | Low | Interactive, latency-critical |
5 |
64 KB |
+5 ms | High | Balanced — safe default |
20 |
256 KB |
+20 ms | Very high | Bulk ingestion, analytics |
100 |
1 MB |
+100 ms | Maximum | ETL, batch-only pipelines |
linger.ms=5 and batch.size=65536 (64 KB) is the safe starting point for most workloads. Tune upward only after measuring broker write amplification — the ratio of individual produce requests to actual messages. If write amplification is near 1.0, batching is not working. If it is near 0.02 (1 request per 50 messages), batching is effective.
1.3 Measuring Batching Effectiveness
2. Compression
2.1 Codec Comparison
| Codec | Compression ratio | CPU cost | Decompression speed | Recommended |
|---|---|---|---|---|
none |
1× (no compression) | None | N/A | Only for binary payloads already compressed |
gzip |
Best (5–7×) | High | Slow | ❌ Legacy only — CPU expensive |
snappy |
Moderate (2–4×) | Low | Fast | Low-CPU environments only |
lz4 |
Moderate (3–5×) | Very low | Fastest | Latency-critical, small messages |
zstd |
Best-in-class (5–8×) | Moderate | Fast | ✅ Modern default — all new topics |
zstd is the modern default. It achieves the best compression ratio at moderate CPU cost. In KafkaJS, zstd is not bundled — install @kafkajs/zstd and register CompressionCodecs[CompressionTypes.ZSTD] = ZstdCodec at startup. Requires Kafka broker 2.1+ and the matching client codec on every consumer. For Java/Confluent clients, zstd is available natively — no additional package required.
2.2 Where Compression Happens
The Kafka broker stores and forwards the compressed batch without decompressing. CPU for compression is paid once at the producer; CPU for decompression is paid once at the consumer. The broker incurs zero decompression CPU — this is why Kafka recommends enabling compression by default for all topics with JSON or text payloads.
2.3 Compression and Message Size
3. Partition Count Sizing
3.1 The Sizing Formula
3.2 Partition Count Constraints
| Constraint | Impact | Recommendation |
|---|---|---|
| Min: consumer parallelism | Partitions ≥ max expected consumer count | Plan for peak scale-out |
| Max: broker overhead | Each partition = open file descriptor + leader election state | < 4,000 partitions per broker |
| Ordering | Ordering guaranteed only within a partition | Partition key must capture ordering scope |
| Immutability | Partition count can be increased, never decreased | Provision 2–4× headroom upfront |
3.3 The Live Repartition Problem
Never repartition a live topic without a consumer group migration plan. After increasing partition count, the hash ring changes — the same key maps to a different partition. Existing in-flight messages for a key will be on the old partition; new messages will be on the new partition. Any consumer that joins after repartitioning will see events for the same business entity out of order. The safe procedure always includes draining to lag=0 before the partition count change.
4. acks and Durability
4.1 The Three Durability Levels
acks |
Durability | Latency added | Use case |
|---|---|---|---|
0 |
None | 0 ms | Metrics, sampling, telemetry |
1 |
Leader only | ~1 ms | Application logs, non-critical events |
-1 (all) |
Full ISR quorum | ~5–20 ms | Business events, financial data |
4.2 Idempotent Producer
Summary
| Concept | Rule |
|---|---|
| Batching defaults | linger.ms=5 + batch.size=64 KB is a safe starting point for most throughput workloads; tune upward only after measuring broker write amplification. |
| Compression default | zstd is the modern default compression codec: best compression ratio, moderate CPU — prefer it over gzip (slow) or snappy (low ratio). |
| Partition immutability | Never repartition a live topic without a consumer group migration plan; ordering within the same key breaks during the transition window. |
What's Next
Part 12: Testing Async Systems — Contract Tests, Embedded Brokers, and Chaos Injection closes Series 1 with the testing problem that every async system eventually faces: how do you write reliable, fast, deterministic tests for a system whose correctness depends on a broker you don't control? Embedded Kafka, mock brokers, Pact contract tests, and targeted chaos injection give you the full testing pyramid for async pipelines.
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.