8.3 Compression Strategies
Module Overview
Goal: Choose and tune compression for optimal performance and cost.
What You'll Learn:
- How compression works in Kafka producers
- Compression algorithms: gzip, snappy, lz4, zstd
- Trade-offs: CPU vs network vs disk
- When to use compression
- Measuring compression effectiveness
- Tuning compression for your workload
Prerequisites:
- Module 7.1 (Producer Internals) — understand batching
- Module 8.1 (Batching & Buffering)
1. How Compression Works in Kafka
Compression Lifecycle
Application Thread Sender Thread
│ │
├─ send(record1) │
├─ send(record2) │
├─ send(record3) │
│ │
│ [Records added to batch, UNCOMPRESSED]
│ │
│ ├─ Fetch batch
│ ├─ Compress entire batch
│ │ (gzip/snappy/lz4/zstd)
│ └─ Send compressed bytes
Broker Consumer
│ │
├─ Store compressed │
│ (saves disk space) │
│ │
│──────── compressed data ──────────→│
│
├─ Decompress
└─ Process records
Key Points:
- Compression happens per batch (not per record)
- Producer compresses, broker stores compressed, consumer decompresses
- Larger batches → Better compression ratios
Configuration
props.put("compression.type", "lz4"); // or "none", "gzip", "snappy", "zstd"
2. Compression Algorithms
Overview Table
| Algorithm | Compression Ratio | Speed | CPU Usage | Kafka Version |
|---|---|---|---|---|
| none | 1:1 (no compression) | Fastest | None | All |
| lz4 | Medium (2-3:1) | Very Fast | Low | 0.8.2+ |
| snappy | Medium (2-3:1) | Fast | Low-Medium | 0.8.0+ |
| gzip | High (3-5:1) | Slow | High | All |
| zstd | High (3-5:1) | Fast | Medium | 2.1.0+ |
Algorithm 1: None (No Compression)
props.put("compression.type", "none");
Characteristics:
- No CPU overhead
- Maximum network bandwidth usage
- Fastest (no processing)
Use When:
- Messages already compressed (images, videos)
- Network bandwidth abundant
- CPU constrained
- Ultra-low latency required
Algorithm 2: LZ4
props.put("compression.type", "lz4");
Characteristics:
- Excellent balance: speed vs compression
- Very low CPU overhead
- Good compression ratio (2-3:1 for text)
- Fast decompression
Use When:
- Default choice for most use cases
- Need good compression without CPU cost
- Mixed workload (text + binary)
Benchmark (1MB text data):
- Compression time: ~10ms
- Compressed size: ~350 KB (2.8:1)
- Decompression time: ~5ms
Algorithm 3: Snappy
props.put("compression.type", "snappy");
Characteristics:
- Similar to LZ4 (slightly older)
- Fast compression/decompression
- Good compression ratio (2-3:1)
- Widely supported
Use When:
- Need proven, stable compression
- Compatibility with older Kafka versions
- Similar performance to LZ4
Benchmark (1MB text data):
- Compression time: ~12ms
- Compressed size: ~400 KB (2.5:1)
- Decompression time: ~6ms
Algorithm 4: GZIP
props.put("compression.type", "gzip");
Characteristics:
- Best compression ratio (3-5:1)
- Slowest compression
- High CPU usage
- Slow decompression
Use When:
- Network bandwidth very limited
- Storage cost is critical
- Latency not important
- CPU abundant
Benchmark (1MB text data):
- Compression time: ~80ms
- Compressed size: ~250 KB (4:1)
- Decompression time: ~30ms
Algorithm 5: ZSTD (Zstandard)
props.put("compression.type", "zstd");
Characteristics:
- Best balance: compression ratio + speed
- High compression ratio (similar to gzip)
- Much faster than gzip
- Tunable compression level (see below)
Use When:
- Need high compression without gzip slowness
- Kafka 2.1.0+
- Best choice if version supports it
Benchmark (1MB text data):
- Compression time: ~25ms
- Compressed size: ~280 KB (3.6:1)
- Decompression time: ~10ms
Tuning Compression Level (GZIP and ZSTD)
For gzip and zstd, you can tune the compression level to balance speed vs. ratio.
GZIP Compression Level
props.put("compression.type", "gzip");
props.put("compression.gzip.level", 6); // Range: 1-9 (default: 6)
Levels:
- 1: Fastest compression, lowest ratio (~2:1)
- 6: Default balance (~4:1)
- 9: Best compression, slowest (~4.5:1)
Example:
// Fast gzip (less CPU, lower ratio)
props.put("compression.gzip.level", 1);
// Default gzip (balanced)
props.put("compression.gzip.level", 6);
// Maximum gzip (high CPU, best ratio)
props.put("compression.gzip.level", 9);
When to tune:
- Use level 1-3 if CPU is constrained
- Use level 6 (default) for most cases
- Use level 9 if network/storage is very expensive
ZSTD Compression Level
props.put("compression.type", "zstd");
props.put("compression.zstd.level", 3); // Range: -5 to 22 (default: 3)
Levels:
- -5 to 0: Ultra-fast, lower ratio (rarely used)
- 1-3: Fast, good ratio (recommended)
- 4-9: Slower, better ratio
- 10-22: Very slow, maximum ratio (rarely useful)
Example:
// Fast zstd (low latency)
props.put("compression.zstd.level", 1);
// Default zstd (balanced)
props.put("compression.zstd.level", 3);
// High compression zstd
props.put("compression.zstd.level", 9);
Recommendation:
- Level 1-3: Best for real-time systems (default 3 is great)
- Level 4-6: If you need more compression and can tolerate latency
- Level 7+: Rarely useful (gzip-level slowness)
Comparison: GZIP vs ZSTD Levels
| Level | GZIP Time | GZIP Ratio | ZSTD Time | ZSTD Ratio |
|---|---|---|---|---|
| Low | 40ms (1) | 2.5:1 | 15ms (1) | 3.2:1 |
| Med | 80ms (6) | 4.0:1 | 25ms (3) | 4.0:1 |
| High | 120ms (9) | 4.5:1 | 60ms (9) | 4.8:1 |
Key Insight: ZSTD level 3 achieves similar compression to GZIP level 6, but 3x faster.
When NOT to Tune Compression Level
Don't tune if:
- You're using
lz4orsnappy(no tunable levels) - You're using default settings and they work fine
- You haven't measured a bottleneck
Do tune if:
- CPU is a bottleneck (lower level)
- Network/storage is a bottleneck (higher level)
- You've benchmarked and know the impact
3. Compression Effectiveness by Data Type
Text Data (JSON, Logs, CSV)
Compression Ratios:
Type: JSON
Original: 1000 bytes
none: 1000 bytes (1.0:1)
lz4: 350 bytes (2.9:1) ✅ Good
snappy: 380 bytes (2.6:1) ✅ Good
gzip: 220 bytes (4.5:1) ✅ Best ratio
zstd: 240 bytes (4.2:1) ✅ Best balance
Recommendation: Use lz4 or zstd (balance speed + ratio).
Binary Data (Protobuf, Avro)
Compression Ratios:
Type: Protobuf/Avro
Original: 1000 bytes
none: 1000 bytes (1.0:1)
lz4: 750 bytes (1.3:1) ⚠️ Limited
snappy: 780 bytes (1.3:1) ⚠️ Limited
gzip: 650 bytes (1.5:1) ⚠️ Limited
zstd: 680 bytes (1.5:1) ⚠️ Limited
Recommendation: Binary formats already compact. Use lz4 for modest gains or none if CPU matters.
Already Compressed (Images, Videos)
Compression Ratios:
Type: JPEG image
Original: 100 KB
none: 100 KB (1.0:1)
lz4: 100 KB (1.0:1) ❌ No gain
snappy: 100 KB (1.0:1) ❌ No gain
gzip: 101 KB (0.99:1) ❌ Actually larger!
zstd: 100 KB (1.0:1) ❌ No gain
Recommendation: Use none (waste of CPU).
4. Trade-Off Analysis
CPU vs Network Trade-Off
Scenario: 1 GB of data to send
No Compression (none):
Compression CPU: 0 ms
Network transfer (1 Gbps): 8 seconds
Total: 8 seconds
LZ4 Compression:
Compression CPU: 100 ms
Compressed size: 350 MB
Network transfer: 2.8 seconds
Total: 2.9 seconds
✅ 5.1 seconds saved (63% faster)
GZIP Compression:
Compression CPU: 800 ms
Compressed size: 250 MB
Network transfer: 2 seconds
Total: 2.8 seconds
✅ 5.2 seconds saved (65% faster)
❌ But 8x more CPU than lz4
Conclusion: Compression usually worth it unless network is very fast or CPU very limited.
Network Speed Impact
| Network Speed | Best Choice | Rationale |
|---|---|---|
| 10 Gbps+ | none or lz4 | Network fast, save CPU |
| 1 Gbps | lz4 or zstd | Balance speed + compression |
| 100 Mbps | zstd or gzip | Network bottleneck, compress more |
| < 10 Mbps | gzip | Network very slow, max compression |
5. Measuring Compression Effectiveness
Key Metrics
1. Compression Ratio
kafka.producer:type=producer-topic-metrics,client-id=<id>,topic=<topic>,name=compression-rate
→ Compression ratio (< 1.0 = effective)
// Example values:
// 0.3 = 3.3:1 compression (70% reduction)
// 0.5 = 2:1 compression (50% reduction)
// 1.0 = No compression
2. Request Size
kafka.producer:type=producer-metrics,client-id=<id>,name=request-size-avg
→ Average request size (bytes)
// Compare with/without compression:
// No compression: 100 KB avg request
// LZ4 compression: 35 KB avg request
// → 65% reduction ✅
3. Record Send Rate
kafka.producer:type=producer-metrics,client-id=<id>,name=record-send-rate
→ Records per second
// With compression, throughput may increase
// (network no longer bottleneck)
Benchmarking Compression
public class CompressionBenchmark {
public static void main(String[] args) {
String[] compressionTypes = {"none", "lz4", "snappy", "gzip", "zstd"};
for (String compression : compressionTypes) {
System.out.println("\n=== Testing: " + compression + " ===");
Properties props = createProps();
props.put("compression.type", compression);
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
String data = generateTestData(1000); // 1 KB
producer.send(new ProducerRecord<>("test", "key-" + i, data));
}
producer.flush();
long duration = System.currentTimeMillis() - start;
System.out.println("Duration: " + duration + " ms");
System.out.println("Throughput: " + (10000.0 / duration * 1000) + " msgs/sec");
producer.close();
}
}
}
6. Best Practices
✅ Best Practice 1: Start with LZ4
props.put("compression.type", "lz4");
Rationale:
- Excellent default choice
- Low CPU overhead
- Good compression ratio
- Fast decompression
✅ Best Practice 2: Enable Compression for Text Data
// JSON, logs, text messages
props.put("compression.type", "lz4"); // or zstd
Savings: 50-70% bandwidth reduction.
✅ Best Practice 3: Disable Compression for Pre-Compressed Data
// Images, videos, already compressed formats
props.put("compression.type", "none");
Rationale: No benefit, wastes CPU.
✅ Best Practice 4: Use ZSTD for Maximum Efficiency (if available)
// Kafka 2.1.0+
props.put("compression.type", "zstd");
Best of both worlds: High compression, reasonable speed.
✅ Best Practice 5: Monitor Compression Ratio
// Alert if compression ineffective
if (compressionRate > 0.9) {
logger.warn("Compression not effective: {}", compressionRate);
// Consider disabling or investigating data
}
7. Compression + Batching Synergy
Why Larger Batches Compress Better
Small batch (1 record, 1 KB):
Compressed: 950 bytes (1.05:1) ❌ Poor
Medium batch (10 records, 10 KB):
Compressed: 4 KB (2.5:1) ✅ Good
Large batch (100 records, 100 KB):
Compressed: 30 KB (3.3:1) ✅ Better
Reason: Compression algorithms find more patterns in larger data.
Optimal Configuration
// Combine large batches with compression
props.put("batch.size", 65536); // 64 KB
props.put("linger.ms", 10); // Allow batching
props.put("compression.type", "lz4"); // Compress large batches
// Result: Maximum compression effectiveness
8. Compression Strategies by Use Case
Use Case 1: High-Volume Logs
Requirements:
- 500,000 msgs/sec
- Average size: 500 bytes (text)
- Network: 1 Gbps
Configuration:
props.put("compression.type", "lz4");
props.put("batch.size", 65536);
props.put("linger.ms", 10);
Result:
- Compression ratio: 3:1
- Network usage: 83 MB/sec (instead of 250 MB/sec)
- CPU overhead: < 5%
Use Case 2: Real-Time Metrics
Requirements:
- 10,000 msgs/sec
- Average size: 200 bytes
- Latency: < 10ms
Configuration:
props.put("compression.type", "none"); // Save CPU, prioritize latency
props.put("batch.size", 8192);
props.put("linger.ms", 0);
Rationale: Low volume, latency critical, compression overhead not worth it.
Use Case 3: JSON API Events
Requirements:
- 50,000 msgs/sec
- Average size: 2 KB (JSON)
- Network: 100 Mbps
Configuration:
props.put("compression.type", "zstd"); // Max compression (network constrained)
props.put("batch.size", 131072); // 128 KB
props.put("linger.ms", 20);
Result:
- Compression ratio: 4:1
- Network usage: 25 MB/sec (instead of 100 MB/sec)
- Fits within network capacity ✅
9. Common Mistakes
❌ Mistake 1: Compressing Already Compressed Data
// Images, videos
props.put("compression.type", "gzip"); // Wastes CPU, no benefit
Fix:
props.put("compression.type", "none");
❌ Mistake 2: Using GZIP for Latency-Sensitive Applications
props.put("compression.type", "gzip"); // Slow compression
Fix:
props.put("compression.type", "lz4"); // Fast compression
❌ Mistake 3: Not Monitoring Compression Effectiveness
Problem: Compression enabled but not working.
Fix: Monitor compression-rate metric:
if (compressionRate > 0.8) {
// Compression not effective — investigate
}
❌ Mistake 4: Small Batches with Compression
props.put("batch.size", 1024); // 1 KB batches
props.put("compression.type", "gzip"); // Won't compress well
Fix: Increase batch size for better compression:
props.put("batch.size", 16384); // 16 KB
10. Advanced: Compression Level Tuning (ZSTD)
ZSTD Compression Levels
ZSTD supports configurable compression levels (1-22).
Note: Kafka doesn't expose this directly, but you can set via JVM properties.
Level 1: Fastest, lowest compression (~2.5:1)
Level 3: Default (~3.5:1)
Level 9: Balanced (~4:1)
Level 19: Maximum compression (~5:1, very slow)
Trade-off:
- Higher level → Better compression, slower
- Lower level → Faster, less compression
Summary
Key Takeaways
✅ Compression saves network and disk at the cost of CPU
✅ LZ4: Best default choice (fast, good ratio)
✅ ZSTD: Best overall (if Kafka 2.1.0+)
✅ GZIP: Maximum compression, but slow
✅ Snappy: Similar to LZ4, older
✅ None: For pre-compressed data or ultra-low latency
✅ Larger batches compress better — combine with batching
✅ Text data compresses well (3-5:1), binary less so
✅ Monitor compression-rate to verify effectiveness
Compression Decision Tree
Data type?
├─ Text (JSON, logs, CSV)
│ ├─ Network fast (> 1 Gbps) → lz4
│ ├─ Network medium → zstd
│ └─ Network slow → gzip
│
├─ Binary (Protobuf, Avro)
│ └─ lz4 (modest gains) or none
│
└─ Pre-compressed (images, videos)
└─ none
Quick Reference
| Data Type | Network | Latency Sensitive? | Recommendation |
|---|---|---|---|
| Text | Fast | Yes | lz4 |
| Text | Fast | No | zstd |
| Text | Slow | Yes | lz4 |
| Text | Slow | No | gzip or zstd |
| Binary | Any | Yes | none or lz4 |
| Binary | Slow | No | lz4 |
| Pre-compressed | Any | Any | none |
Configuration Checklist
- Identify data type (text, binary, pre-compressed)
- Measure baseline (no compression)
- Enable compression (start with lz4)
- Monitor compression-rate metric
- Measure impact on throughput and latency
- Adjust if needed (try zstd for better ratio)
- Document final choice and rationale
What's Next
- Module 9.1: Practical Producer Patterns & Real-World Scenarios
Apply all tuning knowledge to production patterns
Previous Module: ← 8.2 - Latency vs Throughput Tuning
Next Module: 9.1 - Practical Producer Patterns & Real-World Scenarios →