5.1 Performance Tuning & Capacity Planning
Module Overview
Goal: One authoritative place for performance decisions and capacity planning.
What You'll Learn:
- Broker-level tuning configurations
- Disk vs network constraint identification
- JVM tuning for Kafka
- Capacity planning methodology
- Scaling strategies (horizontal vs vertical)
- Performance testing and benchmarking
- Common performance bottlenecks and solutions
What's NOT Covered Here:
- Topic design fundamentals (covered in Module 2.2)
- Client-side tuning (covered in Producer/Consumer modules)
- Monitoring metrics (covered in Module 4.x)
Key Insight: Performance tuning is about identifying bottlenecks and systematically removing them. Capacity planning is about staying ahead of growth.
1. Performance Tuning Philosophy
The Tuning Process
1. MEASURE (Baseline)
└─ Collect current performance metrics
2. IDENTIFY (Bottleneck)
└─ Find the limiting factor (disk, network, CPU)
3. TUNE (Single Change)
└─ Change one config at a time
4. MEASURE (Validate)
└─ Did performance improve?
5. REPEAT
└─ Move to next bottleneck
NEVER:
└─ Change multiple configs simultaneously
└─ Tune without measuring impact
Common Bottlenecks
DISK I/O:
├─ Symptoms: High I/O wait, slow flush times
└─ Solution: SSDs, more disks, tune flush settings
NETWORK:
├─ Symptoms: High network utilization, dropped packets
└─ Solution: Faster network, compression, add brokers
CPU:
├─ Symptoms: High CPU usage, slow request processing
└─ Solution: Tune compression, add brokers
MEMORY:
├─ Symptoms: High GC time, OOM errors
└─ Solution: Increase heap, tune GC
COORDINATION:
├─ Symptoms: Slow controller operations, high ZK latency
└─ Solution: Tune ZK, migrate to KRaft
2. Broker-Level Tuning
Thread Pool Configuration
num.network.threads
# Network threads handling requests/responses
num.network.threads=8 # Default: 3
Recommendation:
└─ Set to number of CPU cores (or 2x for high network traffic)
Impact:
├─ More threads = better network concurrency
└─ Too many = context switching overhead
num.io.threads
# Threads handling disk I/O
num.io.threads=16 # Default: 8
Recommendation:
└─ Set to 2x number of disks (for HDDs)
└─ Can be lower for SSDs (less disk seek time)
Impact:
├─ More threads = better disk I/O concurrency
└─ Must balance with actual disk capacity
num.replica.fetchers
# Threads per broker for follower replication
num.replica.fetchers=4 # Default: 1
Recommendation:
└─ Increase if replication lag is high
Impact:
├─ More threads = faster replication
└─ Higher network and disk usage
Socket Buffer Configuration
socket.send.buffer.bytes & socket.receive.buffer.bytes
# Socket buffer sizes (bytes)
socket.send.buffer.bytes=102400 # Default: 100 KB
socket.receive.buffer.bytes=102400 # Default: 100 KB
Recommendation:
└─ Increase for high-throughput, high-latency networks
└─ 1 MB (1048576) for 10 Gbps network
Impact:
├─ Larger buffers = better throughput on fast networks
└─ More memory usage per connection
socket.request.max.bytes
# Max size of request broker will accept
socket.request.max.bytes=104857600 # Default: 100 MB
Recommendation:
└─ Match with max.message.bytes if large messages
Impact:
└─ Prevents oversized requests from consuming resources
Background Thread Configuration
background.threads
# Threads for background tasks (log cleanup, etc.)
background.threads=10 # Default: 10
Recommendation:
└─ Default is usually sufficient
└─ Increase if log cleanup lagging
Impact:
└─ More threads = faster background processing
log.cleaner.threads
# Threads for log compaction
log.cleaner.threads=1 # Default: 1
Recommendation:
└─ Increase to 2-4 if using many compacted topics
Impact:
└─ More threads = faster compaction
└─ Higher CPU and disk usage
3. Disk Performance Tuning
Use SSDs
HDD vs SSD COMPARISON:
HDD:
├─ IOPS: 100-200
├─ Latency: 10-20 ms
├─ Cost: Low
└─ Use case: Archival, low-throughput
SSD:
├─ IOPS: 10,000+
├─ Latency: < 1 ms
├─ Cost: Higher
└─ Use case: Production, high-throughput
RECOMMENDATION:
└─ Use SSDs for Kafka brokers in production
└─ 10-100x performance improvement
Filesystem Choice
EXT4 vs XFS:
EXT4:
├─ Widely used, stable
├─ Good performance
└─ Default for many Linux distributions
XFS:
├─ Better for large files
├─ Better parallel I/O
├─ Recommended by Confluent
└─ Use with: mkfs.xfs /dev/sdX
RECOMMENDATION:
└─ Use XFS for Kafka data directories
Mount Options
# /etc/fstab entry for Kafka data directory
/dev/sdX /var/kafka xfs defaults,noatime,nodiratime 0 2
Key options:
├─ noatime: Don't update access time (less I/O)
├─ nodiratime: Don't update directory access time
└─ defaults: Standard mount options
Flush Settings
log.flush.interval.messages & log.flush.interval.ms
# Flush to disk after N messages or T milliseconds
log.flush.interval.messages=10000 # Default: Long.MAX_VALUE (disabled)
log.flush.interval.ms=1000 # Default: null (disabled)
Recommendation:
└─ Leave DISABLED (rely on OS page cache and replication)
└─ Flushing is expensive (reduces throughput)
When to enable:
└─ Only if replication factor = 1 (not recommended!)
└─ Compliance requirements (rare)
4. Network Performance Tuning
Enable Compression
# Broker-side compression (if producer doesn't compress)
compression.type=producer # Default: producer
Options:
├─ producer: Use producer's compression
├─ none: No compression
├─ gzip, snappy, lz4, zstd: Force recompression
Recommendation:
└─ Use 'producer' (let producers compress)
└─ If mixed producers, enforce 'lz4' or 'zstd' at broker
Impact:
Compression reduces:
├─ Network bandwidth (3-5x)
├─ Disk usage (3-5x)
└─ Replication traffic
Trade-off:
└─ Higher CPU usage (compression/decompression)
Network Tuning (OS-Level)
# /etc/sysctl.conf (Linux kernel tuning)
# Increase network buffer sizes
net.core.rmem_max=134217728 # 128 MB
net.core.wmem_max=134217728 # 128 MB
net.ipv4.tcp_rmem=4096 87380 134217728
net.ipv4.tcp_wmem=4096 65536 134217728
# Increase max connections
net.core.somaxconn=4096
net.ipv4.tcp_max_syn_backlog=4096
# Enable TCP window scaling
net.ipv4.tcp_window_scaling=1
# Increase connection tracking
net.netfilter.nf_conntrack_max=1000000
# Apply changes
sysctl -p
5. OS-Level Optimizations
File Descriptor Limits
Why File Descriptors Matter:
Kafka brokers need many open files simultaneously:
- Log segment files (per partition)
- Index files
- Client connections (sockets)
- Inter-broker connections
# Check current limit
ulimit -n
# Common default: 1024 (way too low for Kafka!)
# Check Kafka process file descriptor usage
lsof -p $(pgrep -f kafka.Kafka) | wc -l
Setting File Descriptor Limits:
# Temporary (current session only)
ulimit -n 100000
# Permanent: Edit /etc/security/limits.conf
kafka soft nofile 100000
kafka hard nofile 100000
# OR for systemd service: Edit /etc/systemd/system/kafka.service
[Service]
LimitNOFILE=100000
# Reload systemd and restart
systemctl daemon-reload
systemctl restart kafka
# Verify after restart
cat /proc/$(pgrep -f kafka.Kafka)/limits | grep "open files"
# Should show: 100000 (soft) and 100000 (hard)
Recommended Limits:
Small cluster (< 10 brokers, < 1000 partitions):
└─ 50,000 file descriptors
Medium cluster (10-50 brokers, 1000-10000 partitions):
└─ 100,000 file descriptors
Large cluster (> 50 brokers, > 10000 partitions):
└─ 200,000+ file descriptors
Formula (rough estimate):
└─ FD limit = (partitions × 3) + (connections × 2) + 10000
Example: (5000 × 3) + (1000 × 2) + 10000 = 27,000
Disk I/O Scheduler
Why It Matters:
Traditional HDDs need I/O scheduling to minimize disk seeks. SSDs and NVMe drives don't benefit from this — in fact, scheduling adds unnecessary latency!
# Check current scheduler
cat /sys/block/sda/queue/scheduler
# Output: [mq-deadline] kyber none
# [brackets] indicate active scheduler
Recommended Schedulers:
FOR SSDs:
└─ Use: noop or none
└─ Why: SSDs have no seek time, scheduling adds latency
FOR NVMe:
└─ Use: none
└─ Why: NVMe is extremely fast, any scheduling is overhead
FOR HDDs (if you must):
└─ Use: deadline
└─ Why: Minimizes latency while preventing starvation
Setting I/O Scheduler:
# Temporary (until reboot)
echo none > /sys/block/nvme0n1/queue/scheduler # For NVMe
echo noop > /sys/block/sda/queue/scheduler # For SSD
# Permanent: Add to kernel boot parameters
# Edit /etc/default/grub:
GRUB_CMDLINE_LINUX="elevator=noop" # For SSDs
# OR
GRUB_CMDLINE_LINUX="elevator=none" # For NVMe
# Update grub and reboot
update-grub
reboot
# Verify after reboot
cat /sys/block/sda/queue/scheduler
# Should show: [none] or [noop]
Performance Impact:
BENCHMARK RESULTS (typical):
HDD with deadline scheduler:
└─ Latency: 10-20ms (acceptable for HDD)
SSD with default scheduler (mq-deadline):
└─ Latency: 1-5ms (scheduler overhead)
SSD with noop/none:
└─ Latency: < 1ms (10x improvement!)
└─ Throughput: +30-50% in some workloads
NVMe with none:
└─ Latency: < 0.1ms (critical for low-latency topics)
Memory Swappiness
What is Swappiness?
vm.swappiness controls how aggressively the Linux kernel swaps memory to disk:
├─ 0: Avoid swapping except absolute emergency
├─ 1: Minimal swapping (recommended for Kafka)
├─ 10: Low swapping preference
├─ 60: Default on most systems
└─ 100: Aggressive swapping (bad for Kafka!)
Why Low Swappiness Matters for Kafka:
Kafka relies heavily on OS page cache:
├─ Kafka writes to page cache (in RAM)
├─ OS flushes to disk asynchronously
├─ Kafka reads from page cache (zero-copy)
└─ Page cache = performance critical!
If memory is swapped:
├─ Page cache evicted to make room for swap
├─ Disk I/O increases dramatically
├─ Latency spikes (10-100x slower)
└─ Throughput collapses
Setting Swappiness:
# Check current value
cat /proc/sys/vm/swappiness
# Default: 60 (too high for Kafka)
# Temporary (until reboot)
sysctl vm.swappiness=1
# Permanent: Add to /etc/sysctl.conf
vm.swappiness=1
# Apply immediately
sysctl -p
# Verify
cat /proc/sys/vm/swappiness
# Should show: 1
Recommended Values:
vm.swappiness=1 (RECOMMENDED)
├─ Minimal swapping
├─ Maximizes page cache usage
└─ Emergency swapping still available
vm.swappiness=0 (AGGRESSIVE)
├─ Disable swapping entirely (except OOM)
├─ Risk: Out-of-memory killer may terminate processes
└─ Use only if you're confident in memory sizing
Monitoring Swap Usage:
# Check current swap usage
free -h
# total used free shared buff/cache available
# Mem: 62Gi 30Gi 10Gi 1.0Gi 22Gi 30Gi
# Swap: 8.0Gi 0B 8.0Gi ← Should be 0!
# If swap usage > 0, investigate memory pressure
vmstat 1 5
# Watch "si" (swap in) and "so" (swap out) columns
# Should be 0 or very low
# Check which processes are swapping
for file in /proc/*/status ; do
awk '/VmSwap|Name/{printf $2 " " $3}END{ print ""}' $file;
done | sort -k 2 -n -r | head -10
Complete OS Tuning Script
#!/bin/bash
# kafka-os-tuning.sh - Apply OS-level optimizations for Kafka
set -e
echo "=== Kafka OS Tuning Script ==="
# 1. File Descriptor Limits
echo "Setting file descriptor limits..."
cat >> /etc/security/limits.conf << EOF
kafka soft nofile 100000
kafka hard nofile 100000
EOF
# 2. Swappiness
echo "Setting vm.swappiness=1..."
echo "vm.swappiness=1" >> /etc/sysctl.conf
sysctl -p
# 3. Network Buffers (already in section 4)
echo "Optimizing network buffers..."
cat >> /etc/sysctl.conf << EOF
net.core.rmem_max=134217728
net.core.wmem_max=134217728
net.ipv4.tcp_rmem=4096 87380 134217728
net.ipv4.tcp_wmem=4096 65536 134217728
net.core.somaxconn=4096
net.ipv4.tcp_max_syn_backlog=4096
net.ipv4.tcp_window_scaling=1
EOF
sysctl -p
# 4. Disk Scheduler (for SSDs/NVMe)
echo "Setting I/O scheduler..."
DISK=$(lsblk -d -o NAME,TYPE | grep disk | head -1 | awk '{print $1}')
if [[ $DISK == nvme* ]]; then
echo "Detected NVMe drive, setting scheduler to 'none'"
echo none > /sys/block/$DISK/queue/scheduler
else
echo "Detected SSD/HDD, setting scheduler to 'noop'"
echo noop > /sys/block/$DISK/queue/scheduler 2>/dev/null || echo none > /sys/block/$DISK/queue/scheduler
fi
# Make permanent via grub
sed -i 's/GRUB_CMDLINE_LINUX="/GRUB_CMDLINE_LINUX="elevator=none /' /etc/default/grub
update-grub
echo "✓ OS tuning complete!"
echo "✓ Reboot required for all changes to take effect"
OS Tuning Verification Checklist
# After applying OS tuning, verify:
# 1. File descriptors
ulimit -n # Should show 100000+
# 2. Swappiness
cat /proc/sys/vm/swappiness # Should show 1
# 3. Disk scheduler
cat /sys/block/sda/queue/scheduler # Should show [none] or [noop]
# 4. No swap usage
free -h | grep Swap # used should be 0B or minimal
# 5. Network buffers
sysctl net.core.rmem_max # Should show 134217728
# All checks pass? Ready for production! ✓
6. JVM Tuning
Heap Size Configuration
# Set heap size (4-16 GB recommended)
export KAFKA_HEAP_OPTS="-Xms8g -Xmx8g"
# Smaller brokers (light traffic)
export KAFKA_HEAP_OPTS="-Xms4g -Xmx4g"
# Larger brokers (heavy traffic)
export KAFKA_HEAP_OPTS="-Xms16g -Xmx16g"
RULES:
├─ Set -Xms == -Xmx (avoid resizing)
├─ Don't exceed 32 GB (compressed pointers disabled)
├─ Leave at least 50% RAM for OS page cache
└─ Monitor heap usage and GC time
Garbage Collection Tuning
G1GC (Recommended)
# G1 Garbage Collector (default in Java 9+)
export KAFKA_JVM_PERFORMANCE_OPTS="-XX:+UseG1GC \
-XX:MaxGCPauseMillis=20 \
-XX:InitiatingHeapOccupancyPercent=35 \
-XX:G1ReservePercent=20"
Benefits:
├─ Low pause times (< 20ms typical)
├─ Predictable latency
├─ Good throughput
└─ Default for Java 9+
Target:
└─ GC pause time < 100ms
└─ < 1% time spent in GC
GC Logging
# Enable GC logging (Java 8)
export KAFKA_GC_LOG_OPTS="-Xloggc:/var/log/kafka/gc.log \
-XX:+PrintGCDetails \
-XX:+PrintGCDateStamps \
-XX:+PrintGCTimeStamps \
-XX:+UseGCLogFileRotation \
-XX:NumberOfGCLogFiles=10 \
-XX:GCLogFileSize=100M"
# Enable GC logging (Java 11+)
export KAFKA_GC_LOG_OPTS="-Xlog:gc*:file=/var/log/kafka/gc.log:time,tags:filecount=10,filesize=100M"
Use GC logs for:
├─ Analyzing GC pause times
├─ Tuning heap size
├─ Identifying memory leaks
└─ Troubleshooting performance issues
JVM Monitoring
# Enable JMX for monitoring
export JMX_PORT=9999
export KAFKA_JMX_OPTS="-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
-Djava.rmi.server.hostname=<broker-hostname>"
Monitor:
├─ Heap usage (target: < 70% of max)
├─ GC count and time
├─ Thread count
└─ Class loading stats
6. Capacity Planning
Capacity Planning Methodology
1. DEFINE REQUIREMENTS:
├─ Target throughput (MB/s, messages/s)
├─ Retention period
├─ Replication factor
├─ Number of topics/partitions
└─ Expected growth rate
2. CALCULATE RESOURCES:
├─ Network bandwidth
├─ Disk capacity
├─ Disk throughput
└─ CPU and memory
3. SIZE CLUSTER:
└─ Number of brokers needed
4. ADD HEADROOM:
└─ 30-50% buffer for growth and failures
5. VALIDATE:
└─ Test with realistic workload
Disk Capacity Calculation
FORMULA:
Disk Capacity = (Daily Ingress Rate) × (Retention Days) × (Replication Factor) × (Growth Buffer)
Example:
├─ Daily ingress: 1 TB/day
├─ Retention: 7 days
├─ Replication factor: 3
├─ Growth buffer: 1.5x (50% buffer)
Calculation:
1 TB × 7 × 3 × 1.5 = 31.5 TB per broker
With 10 brokers:
└─ Total cluster capacity: 315 TB raw
└─ Effective capacity (after replication): 105 TB
Network Bandwidth Calculation
FORMULA:
Network Bandwidth = (Peak Ingress) × (Replication Factor + 1)
The "+1" accounts for consumer traffic
Example:
├─ Peak ingress: 100 MB/s
├─ Replication factor: 3
├─ Consumer traffic: ~1x ingress
Network needed:
100 MB/s × (3 + 1) = 400 MB/s (3.2 Gbps)
Recommendation:
└─ Use 10 Gbps network for production
└─ Ensure network has headroom (< 70% utilization)
Partition Count Planning
FORMULA:
Partitions = max(
Throughput Target / Single Partition Throughput,
Max Consumers Needed
)
Single partition throughput:
├─ Typical: 10-20 MB/s
└─ Depends on message size, compression
Example:
├─ Target throughput: 100 MB/s
├─ Single partition: 10 MB/s
└─ Partitions needed: 10
But also consider:
├─ Consumer parallelism (max consumers = partitions)
├─ Total partitions per broker (< 4000 recommended)
└─ Leadership distribution
Tiered Storage for Capacity Optimization
Modern Approach: Reduce Local Disk Requirements
Instead of storing all data on expensive local broker disks, Tiered Storage (Kafka 3.6+) allows offloading older data to cheap remote storage (S3, Azure Blob, GCS).
TRADITIONAL CAPACITY PLANNING:
Requirements:
├─ Daily ingress: 1 TB/day
├─ Retention: 365 days (1 year)
├─ Replication: 3x
└─ Growth buffer: 1.5x
Disk needed per broker:
1 TB × 365 × 3 × 1.5 = 1,642 TB per broker ⚠️
└─ Cost: Very expensive (SSD required for performance)
TIERED STORAGE APPROACH:
Same requirements, but:
├─ Local retention: 7 days (hot data on SSD)
├─ Remote retention: 358 days (cold data on S3)
└─ Total retention: 365 days
Local disk needed per broker:
1 TB × 7 × 3 × 1.5 = 31.5 TB per broker ✓
└─ Cost: 50x less disk per broker!
Remote storage needed (cluster-wide):
1 TB × 358 days × 1 replication (S3 already durable) = 358 TB
└─ Cost: 90% cheaper than SSD (S3 pricing)
Total savings:
├─ Local disk: 1,642 TB → 31.5 TB (98% reduction!)
├─ Cost: ~10x cheaper overall
└─ Performance: Hot data still fast (on local SSD)
Tiered Storage Capacity Formula:
FORMULA:
Local Disk = (Daily Ingress) × (Local Retention Days) × (Replication Factor) × (Buffer)
Remote Storage = (Daily Ingress) × (Remote Retention Days) × (1)
# No replication needed (S3/Azure handles durability)
Example Calculation:
Requirements:
├─ Daily ingress: 500 GB/day
├─ Total retention: 180 days (6 months)
├─ Replication factor: 3
├─ Local retention (hot): 14 days
├─ Remote retention (cold): 166 days
└─ Growth buffer: 1.3x
Local Disk per Broker:
500 GB × 14 × 3 × 1.3 = 27.3 TB
Remote Storage (Cluster-Wide):
500 GB × 166 = 83 TB
└─ S3 Standard cost: ~$2,000/month
└─ vs SSD cost for same data: ~$20,000/month
Savings: 90% cost reduction! ✓
Capacity Planning Decision Tree:
WHEN TO USE TIERED STORAGE:
✅ Long retention requirements (> 7 days)
└─ Example: Compliance, auditing, analytics
✅ Cost is a major concern
└─ Tiered storage = 10x cheaper for cold data
✅ Consumers rarely access old data
└─ Acceptable latency: 50-200ms for historical data
✅ Want elastic storage without adding brokers
└─ Remote storage grows independently
WHEN TO USE TRADITIONAL (LOCAL ONLY):
✅ Short retention (< 7 days)
└─ All data is "hot", tiered storage adds complexity
✅ All data needs low latency (< 1ms)
└─ Consumers frequently replay historical data
✅ Kafka version < 3.6
└─ Tiered storage not available
✅ Want simplicity over cost
└─ Tiered storage adds operational complexity
Capacity Planning Comparison:
SCENARIO: E-Commerce Platform
├─ Daily traffic: 2 TB/day
├─ Retention requirement: 90 days
├─ Replication factor: 3
┌──────────────────────┬─────────────────┬──────────────────────┐
│ Approach │ Traditional │ Tiered Storage │
├──────────────────────┼─────────────────┼──────────────────────┤
│ Local retention │ 90 days │ 7 days (hot) │
│ Remote retention │ N/A │ 83 days (cold) │
│ │ │ │
│ Local disk/broker │ 810 TB │ 63 TB │
│ Remote storage │ N/A │ 166 TB (S3) │
│ │ │ │
│ Brokers needed (10TB │ 81 brokers ⚠️ │ 7 brokers ✓ │
│ disks each) │ │ │
│ │ │ │
│ Monthly cost │ ~$200,000 │ ~$25,000 │
│ (estimate) │ │ (8x cheaper!) │
│ │ │ │
│ Broker recovery time │ Hours (810 TB) │ Minutes (63 TB) │
│ Operational │ Complex │ Moderate │
│ complexity │ (many brokers) │ (tiered config) │
└──────────────────────┴─────────────────┴──────────────────────┘
Best Practice: Hybrid Approach
RECOMMENDED STRATEGY:
Critical Topics (low-latency, high-value):
├─ Use: Traditional local storage only
├─ Retention: 7-14 days
└─ Example: order-events, payment-transactions
Analytics Topics (high-volume, cold access):
├─ Use: Tiered storage
├─ Local retention: 1-3 days
├─ Remote retention: 30-365 days
└─ Example: clickstream-events, log-aggregation
Audit Topics (compliance, rarely accessed):
├─ Use: Tiered storage
├─ Local retention: 6 hours
├─ Remote retention: 1-7 years
└─ Example: audit-logs, financial-records
RESULT:
├─ Optimize cost where it matters
├─ Keep performance where needed
└─ Meet compliance affordably
Capacity Planning with Tiered Storage Checklist:
☑ Define hot vs cold data boundaries (local.retention.ms)
☑ Calculate local disk for hot data only
☑ Calculate remote storage for cold data
☑ Factor in S3/object storage costs ($0.023/GB/month)
☑ Plan for egress costs if consumers access old data frequently
☑ Consider lifecycle policies (S3 Glacier for very old data)
☑ Monitor remote fetch latency (should be < 200ms)
☑ Test disaster recovery (what if S3 unavailable?)
☑ Document which topics use tiered storage
☑ Review and adjust local retention based on access patterns
7. Scaling Strategies
Horizontal Scaling (Add Brokers)
WHEN TO SCALE HORIZONTALLY:
├─ Disk capacity exhausted
├─ Network saturated
├─ CPU overloaded on all brokers
├─ Need higher throughput
└─ Need more fault tolerance
PROCESS:
1. Add new brokers to cluster
2. Reassign partitions to new brokers
3. Wait for replication to complete
4. Verify load distributed
BENEFITS:
├─ Increases total capacity
├─ Improves fault tolerance
├─ Distributes load
└─ Kafka's native scaling method
CHALLENGES:
└─ Partition reassignment takes time
└─ Temporary increased load during rebalance
Vertical Scaling (Bigger Machines)
WHEN TO SCALE VERTICALLY:
├─ Single broker bottleneck (not cluster-wide)
├─ Need more disk per broker
├─ Need faster network per broker
└─ Short-term quick fix
PROCESS:
1. Decommission broker (move partitions)
2. Replace with larger machine
3. Add back to cluster
4. Reassign partitions
BENEFITS:
├─ Fewer brokers to manage
├─ Less network chatter
└─ Simpler operations
CHALLENGES:
├─ Limited by single-machine limits
├─ More expensive per unit capacity
└─ Harder to scale incrementally
RECOMMENDATION:
└─ Prefer horizontal scaling (Kafka's design)
8. Performance Testing
Benchmarking Tools
kafka-producer-perf-test
# Benchmark producer throughput
kafka-producer-perf-test.sh \
--topic test-topic \
--num-records 10000000 \
--record-size 1000 \
--throughput -1 \
--producer-props \
bootstrap.servers=localhost:9092 \
acks=all \
compression.type=lz4
# Output:
# 500000 records sent, 100000 records/sec (95.37 MB/sec)
# Average latency: 5.00 ms
# Max latency: 100.00 ms
kafka-consumer-perf-test
# Benchmark consumer throughput
kafka-consumer-perf-test.sh \
--topic test-topic \
--messages 10000000 \
--bootstrap-server localhost:9092 \
--group test-group \
--threads 1
# Output:
# 10000000 records consumed in 60 seconds
# 166666.67 records/sec (158.63 MB/sec)
End-to-End Testing
# Test producer → broker → consumer latency
kafka-run-class.sh kafka.tools.EndToEndLatency \
localhost:9092 \
test-topic \
10000 \
1 \
1000
# Output:
# Avg latency: 5.0 ms
# Percentiles: 50th: 3 ms, 99th: 15 ms, 99.9th: 50 ms
9. Common Performance Issues and Solutions
Issue 1: High Producer Latency
SYMPTOM:
└─ request-latency-avg > 100ms
INVESTIGATION:
1. Check broker TotalTimeMs metric
2. Check broker CPU, disk I/O, network
3. Check if acks=all (replication latency)
4. Check if batching enabled (linger.ms)
SOLUTIONS:
├─ If broker overloaded: Add brokers
├─ If disk slow: Use SSDs
├─ If replication slow: Check follower brokers
├─ If network slow: Enable compression
└─ If batching too aggressive: Tune linger.ms
Issue 2: Consumer Lag Growing
SYMPTOM:
└─ Consumer lag increasing steadily
INVESTIGATION:
1. Check consumer processing time
2. Check consumer fetch settings
3. Check partition count vs consumers
4. Check broker throughput capacity
SOLUTIONS:
├─ Slow processing: Optimize app, add consumers
├─ Too few consumers: Add consumers (up to partition count)
├─ Fetch settings: Tune fetch.min.bytes, fetch.max.wait.ms
├─ Broker slow: Scale cluster
└─ Partition bottleneck: Increase partitions (if possible)
Issue 3: Disk Space Filling Up
SYMPTOM:
└─ Disk usage > 85%
INVESTIGATION:
1. Check topic retention settings
2. Check log cleanup running
3. Check if compaction working (if applicable)
4. Identify largest topics
SOLUTIONS:
├─ Reduce retention.ms for non-critical topics
├─ Enable compression (reduces disk usage)
├─ Add disk capacity
├─ Scale brokers horizontally
└─ Review data retention policies
10. Key Takeaways
You've learned:
✅ Performance tuning is iterative: measure, identify, tune, measure
✅ Common bottlenecks: disk I/O, network, CPU, memory, coordination
✅ Thread pools control concurrency (network, I/O, replication)
✅ SSDs provide 10-100x performance over HDDs
✅ JVM heap should be 4-16 GB with G1GC
✅ Capacity planning calculates disk, network, partitions needed
✅ Horizontal scaling is Kafka's native scaling method
✅ Compression reduces network and disk by 3-5x
✅ Benchmarking validates performance improvements
✅ Monitoring is essential for identifying bottlenecks
11. Performance Tuning Checklist
BROKER CONFIGURATION:
☑ num.network.threads = CPU cores
☑ num.io.threads = 2x disks (HDD) or 8-16 (SSD)
☑ socket.send/receive.buffer.bytes = 1 MB (fast network)
☑ compression.type = producer (or lz4/zstd)
☑ log.flush.* = disabled (rely on replication)
INFRASTRUCTURE:
☑ SSDs for data directories
☑ XFS filesystem
☑ noatime mount option
☑ 10 Gbps network
☑ Kernel network tuning applied
JVM:
☑ Heap size: 4-16 GB
☑ -Xms == -Xmx
☑ G1GC enabled
☑ GC logging enabled
☑ < 1% time in GC
CAPACITY:
☑ Disk capacity planned for retention + growth
☑ Network headroom > 30%
☑ Partition count appropriate for throughput
☑ Broker count allows for 1-2 failures
Summary
Performance optimization is about removing bottlenecks:
Tuning Process =
├─ Identify the bottleneck (disk, network, CPU)
├─ Apply targeted fix
├─ Measure improvement
└─ Repeat for next bottleneck
Capacity Planning =
├─ Understand requirements (throughput, retention)
├─ Calculate resources (disk, network, partitions)
├─ Size cluster appropriately
└─ Add 30-50% headroom for growth
Kafka Performance =
├─ Use SSDs (biggest impact)
├─ Tune JVM carefully (avoid over-tuning)
├─ Enable compression (reduces I/O)
├─ Scale horizontally (Kafka's strength)
└─ Monitor continuously (catch issues early)
Remember: Performance tuning is not one-time. As traffic grows and patterns change, revisit tuning decisions. Always measure before and after changes.
Next up: Module 6.1 — Quotas, Throttling & Resource Management — Protect your cluster from abuse and ensure fair resource usage.