13.1 Advanced Configuration & Choosing the Right Tool
Module Overview
Goal: Master advanced configuration and learn when to use Kafka Streams vs alternatives.
What You'll Learn:
- Advanced configuration tuning
- Performance optimization
- Resource tuning (threads, memory, cache)
- RocksDB tuning
- When to use Kafka Streams
- Kafka Streams vs alternatives (ksqlDB, Flink, Spark)
- Use case patterns
- Decision framework
What's NOT Covered Here:
- Basic configuration (covered in Module 3.1)
- Monitoring (covered in Module 12.1)
- Specific operations (covered in earlier modules)
Key Insight: Configuration tuning can dramatically improve performance — choose the right tool for your use case.
1. Thread Configuration
Number of Threads
// Default: 1 thread per instance
props.put(StreamsConfig.NUM_STREAM_THREADS_CONFIG, 4);
// More threads = more parallelism within instance
// But: Max parallelism = partition count
Example:
├─ 10 partitions, 1 instance, 4 threads
│ └─ Each thread handles 2-3 tasks
├─ 10 partitions, 1 instance, 1 thread
│ └─ Thread handles all 10 tasks
└─ 10 partitions, 2 instances, 4 threads each
└─ Each instance handles 5 tasks across 4 threads
Choosing Thread Count
GUIDELINES:
Too few threads (1-2):
├─ Underutilized CPU
├─ Lower throughput
└─ Use when: single-threaded processing needed
Optimal (4-8):
├─ Good CPU utilization
├─ Balanced resource usage
└─ Use when: standard workload
Too many (16+):
├─ Context switching overhead
├─ Diminishing returns
└─ Use when: many partitions (> 50)
Rule of thumb:
└─ num_threads = min(num_partitions, num_cpu_cores)
2. Memory Configuration
Heap Memory
JVM Heap = Java objects (application logic, buffers)
Recommended heap:
├─ Small workload: 2-4 GB
├─ Medium workload: 4-8 GB
├─ Large workload: 8-16 GB
└─ Very large: 16-32 GB
JVM options:
-Xms8g -Xmx8g # 8 GB heap (min = max, avoid resizing)
Avoid:
└─ > 32 GB heap (GC pauses)
Off-Heap Memory (RocksDB)
RocksDB uses off-heap (direct memory)
Estimated off-heap usage:
├─ State store size (GB)
├─ + RocksDB overhead (20-30%)
└─ = Total off-heap
Example:
├─ State stores: 50 GB
├─ Overhead: 15 GB
└─ Total: 65 GB off-heap
Container memory:
├─ Heap: 8 GB
├─ Off-heap: 65 GB
├─ OS overhead: 2 GB
└─ Total: 75 GB
Cache Configuration
// Per-task cache for state updates
props.put(StreamsConfig.CACHE_MAX_BYTES_BUFFERING_CONFIG,
10 * 1024 * 1024); // 10 MB per task
// Global = cache_per_task × num_tasks
Purpose:
├─ Buffer state updates
├─ Reduce changelog writes
└─ Reduce downstream output
Trade-off:
├─ Larger cache = fewer writes, more memory
└─ Smaller cache = more writes, less memory
Recommendations:
├─ High throughput: 50-100 MB
├─ Low latency: 1-10 MB
└─ Balanced: 10-50 MB
3. Commit Interval
Configuration
// How often to commit offsets/transactions
props.put(StreamsConfig.COMMIT_INTERVAL_MS_CONFIG, 30000); // 30 seconds (default)
Impact:
├─ Shorter interval (5-10s): Lower latency, higher overhead
└─ Longer interval (60s+): Higher latency, lower overhead
Choosing Commit Interval
LOW LATENCY (5-10 seconds):
├─ Data visible quickly
├─ Fast recovery (less replay)
├─ Higher broker load
└─ Use when: Real-time dashboards, low-latency requirements
BALANCED (30 seconds):
├─ Good trade-off
├─ Default, works for most cases
└─ Use when: Standard workload
HIGH THROUGHPUT (60+ seconds):
├─ Lower overhead
├─ Higher throughput
├─ Longer recovery time
└─ Use when: Batch processing, high-volume aggregations
4. RocksDB Configuration
What is RocksDB?
RocksDB = embedded key-value store (LSM tree)
Used by Kafka Streams for:
└─ Persistent state stores
Characteristics:
├─ Off-heap (direct memory)
├─ Disk-backed
├─ Fast reads and writes
└─ Tunable performance
Custom RocksDBConfigSetter
public class CustomRocksDBConfig implements RocksDBConfigSetter {
@Override
public void setConfig(String storeName, Options options, Map<String, Object> configs) {
// Increase write buffer for better write performance
options.setWriteBufferSize(64 * 1024 * 1024); // 64 MB
options.setMaxWriteBufferNumber(3);
// Optimize for point lookups
BlockBasedTableConfig tableConfig = new BlockBasedTableConfig();
tableConfig.setBlockSize(16 * 1024); // 16 KB blocks
tableConfig.setCacheIndexAndFilterBlocks(true);
options.setTableFormatConfig(tableConfig);
// Compaction settings
options.setCompactionStyle(CompactionStyle.LEVEL);
options.setMaxBackgroundCompactions(4);
// Memory usage
options.setMaxTotalWalSize(100 * 1024 * 1024); // 100 MB WAL
}
@Override
public void close(String storeName, Options options) {
// Cleanup if needed
}
}
Apply Configuration
props.put(StreamsConfig.ROCKSDB_CONFIG_SETTER_CLASS_CONFIG,
CustomRocksDBConfig.class);
RocksDB Tuning Guidelines
WRITE-HEAVY WORKLOAD:
├─ Increase write_buffer_size (64-128 MB)
├─ Increase max_write_buffer_number (3-5)
└─ Use more compaction threads
READ-HEAVY WORKLOAD:
├─ Increase block_cache_size (100-500 MB)
├─ Use bloom filters
└─ Optimize block_size
LARGE STATE (> 100 GB):
├─ Use SSD (not HDD)
├─ Increase compaction threads
└─ Monitor disk I/O
SMALL STATE (< 10 GB):
├─ Use defaults (usually sufficient)
└─ Consider in-memory stores
5. Producer & Consumer Overrides
Producer Configuration
// Override producer configs
props.put(StreamsConfig.PRODUCER_PREFIX + ProducerConfig.BATCH_SIZE_CONFIG,
32 * 1024); // 32 KB batches
props.put(StreamsConfig.PRODUCER_PREFIX + ProducerConfig.LINGER_MS_CONFIG,
10); // Wait 10ms for batching
props.put(StreamsConfig.PRODUCER_PREFIX + ProducerConfig.COMPRESSION_TYPE_CONFIG,
"lz4"); // Fast compression
// For exactly-once (EOS), these are set automatically:
// enable.idempotence=true
// acks=all
// max.in.flight.requests.per.connection=5
Consumer Configuration
// Override consumer configs
props.put(StreamsConfig.CONSUMER_PREFIX + ConsumerConfig.FETCH_MAX_BYTES_CONFIG,
52428800); // 50 MB
props.put(StreamsConfig.CONSUMER_PREFIX + ConsumerConfig.MAX_POLL_RECORDS_CONFIG,
1000); // 1000 records per poll
props.put(StreamsConfig.CONSUMER_PREFIX + ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG,
45000); // 45 seconds
props.put(StreamsConfig.CONSUMER_PREFIX + ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG,
300000); // 5 minutes
6. Performance Optimization Checklist
1. Topology Optimization
✅ Minimize repartitions
└─ Use groupByKey() instead of groupBy() when possible
✅ Reduce state store count
└─ Combine aggregations
✅ Use stateless operations when possible
└─ map, filter (no state overhead)
✅ Avoid unnecessary materializations
└─ Only materialize what needs querying
2. Parallelism
✅ Partition input topics appropriately
└─ More partitions = more parallelism
✅ Set appropriate thread count
└─ 4-8 threads per instance
✅ Scale horizontally
└─ More instances = more throughput
3. Batching & Buffering
✅ Enable caching
└─ props.put(CACHE_MAX_BYTES_BUFFERING_CONFIG, 50 MB)
✅ Increase batch sizes
└─ producer: batch.size, linger.ms
└─ consumer: fetch.max.bytes, max.poll.records
✅ Use appropriate commit interval
└─ 30-60 seconds for high throughput
4. Compression
✅ Enable compression
└─ props.put(PRODUCER_PREFIX + COMPRESSION_TYPE_CONFIG, "lz4")
Compression types:
├─ lz4: Fast, moderate ratio (recommended)
├─ zstd: Slower, better ratio
├─ snappy: Fast, lower ratio
└─ gzip: Slowest, best ratio
Choose based on:
├─ CPU availability (lz4 if CPU-constrained)
└─ Network bandwidth (zstd if network-constrained)
5. Resource Allocation
✅ Use SSD for state stores
└─ 10-100× faster than HDD
✅ Allocate sufficient memory
└─ Heap + off-heap (RocksDB)
✅ Provision adequate CPU
└─ 4-8 vCPUs per instance
✅ Ensure network bandwidth
└─ 1-10 Gbps for high throughput
7. When to Use Kafka Streams
Ideal Use Cases
✅ Event-driven microservices
└─ Process events, emit results
✅ Real-time aggregations
└─ Counts, sums, windowed metrics
✅ Stream enrichment
└─ Join streams with reference data
✅ Stateful transformations
└─ Sessionization, deduplication
✅ Event sourcing
└─ Maintain materialized views
✅ Real-time analytics
└─ Dashboards, monitoring
When NOT to Use Kafka Streams
❌ Complex SQL queries
└─ Use ksqlDB or external DB
❌ Batch processing (historical data)
└─ Use Spark, Flink batch mode
❌ Machine learning inference (heavy CPU)
└─ Use dedicated ML serving platform
❌ Ad-hoc queries
└─ Use ksqlDB or data warehouse
❌ Very large state (> 1 TB per instance)
└─ Use Flink with external state backend
❌ Sub-millisecond latency
└─ Use in-memory cache (Redis)
8. Kafka Streams vs Alternatives
Kafka Streams vs ksqlDB
KAFKA STREAMS:
✅ Full Java/Scala API (flexibility)
✅ Programmatic control
✅ Embedded in application
✅ Lower latency (no extra hop)
❌ Requires coding
❌ No ad-hoc queries
KSQLDB:
✅ SQL interface (easier)
✅ Ad-hoc queries
✅ Standalone service
❌ Less flexible than code
❌ Additional infrastructure
❌ Higher latency (network hop)
When to choose:
├─ Kafka Streams: Complex logic, embedded processing
└─ ksqlDB: SQL users, ad-hoc queries, simpler use cases
Kafka Streams vs Apache Flink
KAFKA STREAMS:
✅ Simpler (no cluster management)
✅ Embedded in application
✅ Tightly integrated with Kafka
✅ Lower operational overhead
❌ Limited to Kafka (no other sources)
❌ State size limited by disk
❌ No native ML support
APACHE FLINK:
✅ Multiple sources (Kafka, DB, files, etc.)
✅ Advanced features (CEP, ML, etc.)
✅ Large-scale state (external backends)
✅ Powerful windowing
❌ Complex (cluster management)
❌ Higher operational overhead
❌ Steeper learning curve
When to choose:
├─ Kafka Streams: Kafka-centric, simpler operations
└─ Flink: Multiple sources, advanced features, large scale
Kafka Streams vs Spark Streaming
KAFKA STREAMS:
✅ True streaming (record-at-a-time)
✅ Lower latency (milliseconds)
✅ Simpler operations
✅ Tightly integrated with Kafka
❌ Limited to streaming
❌ No batch processing
❌ Smaller ecosystem
SPARK STREAMING:
✅ Unified batch + streaming
✅ Rich ecosystem (ML, graph, etc.)
✅ Structured Streaming (micro-batches)
❌ Higher latency (seconds)
❌ More complex (cluster management)
❌ Micro-batching (not true streaming)
When to choose:
├─ Kafka Streams: Low latency, pure streaming
└─ Spark: Batch + streaming, ML, larger ecosystem
9. Decision Framework
Question 1: What's Your Data Source?
Only Kafka?
└─ Kafka Streams ✅
Kafka + other sources (DB, files, etc.)?
└─ Flink, Spark
SQL queries on Kafka?
└─ ksqlDB
Question 2: What's Your Latency Requirement?
Sub-second (< 1s)?
└─ Kafka Streams ✅
Seconds (1-10s)?
└─ Kafka Streams, Flink, ksqlDB
Minutes (batch)?
└─ Spark, Flink batch mode
Question 3: What's Your State Size?
Small (< 100 GB per instance)?
└─ Kafka Streams ✅
Large (100 GB - 1 TB)?
└─ Kafka Streams with large instances
Very large (> 1 TB)?
└─ Flink with external state backend
Question 4: What's Your Team's Skill Set?
Java/Scala developers?
└─ Kafka Streams ✅
SQL users (analysts)?
└─ ksqlDB
Data engineers (big data)?
└─ Flink, Spark
Question 5: Operational Complexity?
Simple (embedded, no cluster)?
└─ Kafka Streams ✅
Acceptable (standalone cluster)?
└─ ksqlDB, Flink, Spark
10. Architecture Patterns
Pattern 1: Microservice Stream Processor
Use case: Order processing service
Architecture:
├─ Kafka Streams embedded in microservice
├─ Reads from "orders" topic
├─ Validates, enriches, transforms
├─ Writes to "validated-orders" topic
└─ Exposes REST API for queries
Why Kafka Streams:
├─ Tight integration with service
├─ Low latency
└─ Simple deployment
Pattern 2: Real-Time Analytics Pipeline
Use case: Dashboard metrics (counts, sums)
Architecture:
├─ Kafka Streams application
├─ Reads events from multiple topics
├─ Windowed aggregations
├─ Exposes metrics via REST API
└─ Materialized views for queries
Why Kafka Streams:
├─ Real-time aggregations
├─ Interactive queries
└─ Stateful processing
Pattern 3: Stream Enrichment
Use case: Enrich events with reference data
Architecture:
├─ Kafka Streams application
├─ KStream: Event stream (orders)
├─ KTable: Reference data (customers)
├─ Join: Enrich orders with customer data
└─ Write to enriched topic
Why Kafka Streams:
├─ Stream-table join
├─ Low latency
└─ Automatic state management
Pattern 4: Event Sourcing
Use case: Maintain materialized views
Architecture:
├─ Kafka Streams application
├─ Reads event log (source of truth)
├─ Builds materialized views (aggregates)
├─ Exposes views via interactive queries
└─ Views queryable in real-time
Why Kafka Streams:
├─ Stateful aggregations
├─ Interactive queries
└─ Event-driven
11. Migration Strategies
From Batch to Streaming
Step 1: Identify use case
└─ Can it be done incrementally (streaming)?
Step 2: Start small
├─ Single topic, simple transformation
└─ Validate in staging
Step 3: Gradually migrate
├─ One use case at a time
└─ Run batch + streaming in parallel (validate)
Step 4: Retire batch
└─ Once streaming proven reliable
From Another Stream Processor
From Flink/Spark to Kafka Streams:
1. Evaluate fit
└─ Is Kafka the only source?
└─ Is state size manageable?
2. Rewrite topology
└─ Translate Flink/Spark code to Kafka Streams API
3. Test thoroughly
└─ Unit tests, integration tests
4. Deploy canary
└─ Run in parallel, compare results
5. Full migration
└─ Switch traffic, retire old system
12. Cost Optimization
Compute Costs
Reduce instances:
├─ Increase threads per instance
├─ Optimize processing logic
└─ Use larger instances (fewer total)
Example:
├─ Before: 10× small instances (2 vCPU each) = 20 vCPU
└─ After: 5× medium instances (4 vCPU each) = 20 vCPU (cheaper)
Storage Costs
Reduce state store size:
├─ Use compaction (keep latest only)
├─ Set TTL on state (expire old data)
├─ Use in-memory stores (if state small)
└─ Offload to external DB (if state large)
Example:
├─ Before: 100 GB state per instance × 10 instances = 1 TB
└─ After: 10 GB state per instance × 10 instances = 100 GB (90% savings)
Network Costs
Reduce network I/O:
├─ Enable compression (lz4, zstd)
├─ Increase commit interval (fewer writes)
├─ Use caching (buffer updates)
└─ Co-locate Kafka and Streams (same datacenter)
Example:
├─ Before: 10 GB/hour network transfer
└─ After: 5 GB/hour (50% savings with compression)
13. Advanced Patterns
Pattern: Time-Travel Queries
// Query historical state (windowed stores)
ReadOnlyWindowStore<String, Long> store = streams.store(...);
// Query specific time in past
Instant past = Instant.now().minus(Duration.ofHours(24));
WindowStoreIterator<Long> results = store.fetch("key", past, past);
// Use case: "What was the count 24 hours ago?"
Pattern: Multi-Region Deployment
Scenario: Deploy across regions
Architecture:
├─ Kafka cluster per region (mirrored)
├─ Kafka Streams in each region (reads local cluster)
├─ MirrorMaker2 for cross-region replication
└─ Regional failover
Benefits:
├─ Low latency (local reads)
├─ Disaster recovery
└─ Data locality
Pattern: Blue-Green Deployment
Strategy: Zero-downtime deployments
Steps:
1. Deploy new version (green) alongside old (blue)
2. Green joins consumer group → rebalance
3. Both versions process (gradually shift traffic)
4. Validate green version
5. Shut down blue version
Benefit:
└─ Zero downtime, easy rollback
14. Best Practices Summary
Configuration
✅ Set num.stream.threads (4-8)
✅ Enable caching (10-50 MB)
✅ Use EOS v2 for critical apps
✅ Configure commit interval (30-60s)
✅ Tune RocksDB (if large state)
✅ Set appropriate timeouts (session, poll)
✅ Use compression (lz4)
Operations
✅ Use standby replicas (production)
✅ Set uncaught exception handler
✅ Graceful shutdown
✅ Monitor key metrics (state, lag, throughput)
✅ Set up alerts (lag, errors, rebalances)
✅ Log structured events
✅ Test in staging (load, failure, rebalance)
Topology Design
✅ Minimize repartitions
✅ Name state stores explicitly
✅ Reduce state store count
✅ Use appropriate abstractions (KStream vs KTable)
✅ Design for idempotence (external writes)
15. Mental Model Summary
When to Use Kafka Streams
┌───────────────────────────────────────────────────┐
│ KAFKA STREAMS SWEET SPOT │
│ │
│ ✅ Kafka as primary source │
│ ✅ Real-time processing (< 1s latency) │
│ ✅ Stateful transformations │
│ ✅ Stream joins and aggregations │
│ ✅ Embedded in application │
│ ✅ Simple operations (no cluster management) │
│ ✅ Moderate state size (< 100 GB/instance) │
│ │
│ ❌ Multiple sources (not just Kafka) │
│ ❌ Batch processing (historical data) │
│ ❌ Very large state (> 1 TB/instance) │
│ ❌ Complex SQL queries │
│ ❌ Sub-millisecond latency │
└───────────────────────────────────────────────────┘
Tool Comparison
KAFKA STREAMS:
├─ Embedded library
├─ Kafka-native
├─ Millisecond latency
└─ Use when: Simple, Kafka-centric, low-latency
KSQLDB:
├─ Standalone service
├─ SQL interface
├─ Second latency
└─ Use when: SQL users, ad-hoc queries
APACHE FLINK:
├─ Cluster-based
├─ Multiple sources
├─ Advanced features
└─ Use when: Large scale, complex CEP, ML
SPARK STREAMING:
├─ Cluster-based
├─ Batch + streaming
├─ Second latency
└─ Use when: Unified batch/stream, ML
16. Questions You Should Be Able to Answer
After this module, you should be able to answer:
✅ How many threads should I configure?
✅ How do I tune RocksDB performance?
✅ When should I use Kafka Streams vs ksqlDB?
✅ When should I use Kafka Streams vs Flink?
✅ How do I optimize for high throughput?
✅ How do I optimize for low latency?
✅ What are ideal use cases for Kafka Streams?
✅ How do I reduce costs?
17. Conclusion
You've Completed the Kafka Streams Course!
You now understand:
- ✅ Core abstractions (KStream, KTable, GlobalKTable)
- ✅ Stateless and stateful operations
- ✅ Windowing and time-based processing
- ✅ State stores and fault tolerance
- ✅ Exactly-once semantics
- ✅ Joins (stream-stream, stream-table, table-table)
- ✅ Error handling and resilience
- ✅ Interactive queries and testing
- ✅ Monitoring and operations
- ✅ Advanced configuration and tuning
- ✅ When to use Kafka Streams vs alternatives
Next Steps:
- Build a real project
- Deploy to production
- Monitor and iterate
- Contribute to open source
- Share your knowledge
Thank you for learning Kafka Streams!
Summary
You learned:
- ✅ Thread config: 4-8 threads per instance (based on partitions and CPU)
- ✅ Memory: Heap (8-16 GB) + off-heap (state size + 30%)
- ✅ Cache: 10-50 MB per task (balance writes vs memory)
- ✅ Commit interval: 30-60s (balance latency vs throughput)
- ✅ RocksDB tuning: write_buffer_size, block_cache, compaction threads
- ✅ Use Kafka Streams when: Kafka-native, real-time, moderate state, embedded
- ✅ Avoid Kafka Streams when: Multiple sources, batch, very large state, complex SQL
- ✅ Kafka Streams vs ksqlDB: Code vs SQL, embedded vs standalone
- ✅ Kafka Streams vs Flink: Simpler vs advanced features, Kafka-only vs multi-source
- ✅ Optimize: Minimize repartitions, enable compression, scale horizontally, tune resources
This concludes the Kafka Streams course. Go build amazing streaming applications!