12.1 Monitoring & Operating Kafka Streams Applications
Module Overview
Goal: Master monitoring and operations for Kafka Streams in production.
What You'll Learn:
- Key metrics to monitor (JMX)
- Application state and health
- Performance monitoring
- Consumer lag tracking
- Rebalance detection
- Alerting strategies
- Operational best practices
- Troubleshooting
What's NOT Covered Here:
- Configuration tuning (covered in Module 13.1)
- Error handling (covered in Module 10.1)
- State restoration details (covered in Module 6.3)
Key Insight: Production observability is critical — monitor state, lag, throughput, and errors to ensure reliability.
1. Monitoring Overview
Why Monitor?
Production requirements:
├─ Detect failures quickly
├─ Understand performance
├─ Capacity planning
├─ SLA compliance
└─ Troubleshooting
Key questions:
├─ Is the application healthy?
├─ Is it processing data?
├─ Is it falling behind?
├─ Are there errors?
└─ Is a rebalance happening?
Monitoring Layers
1. APPLICATION LEVEL:
└─ State, threads, errors
2. TASK LEVEL:
└─ Processing rate, lag, commits
3. STATE STORE LEVEL:
└─ State size, restore progress
4. TOPOLOGY LEVEL:
└─ Processing per node
5. INFRASTRUCTURE:
└─ CPU, memory, disk, network
2. Application State
KafkaStreams States
CREATED:
└─ Application initialized, not started
REBALANCING:
└─ Consumer group rebalancing (tasks reassigning)
RUNNING:
└─ Healthy, processing records
PENDING_SHUTDOWN:
└─ Shutdown initiated, cleaning up
NOT_RUNNING:
└─ Application stopped
ERROR:
└─ Unrecoverable error occurred
State Listener
KafkaStreams streams = new KafkaStreams(topology, props);
streams.setStateListener((newState, oldState) -> {
logger.info("State transition: {} -> {}", oldState, newState);
switch (newState) {
case RUNNING:
logger.info("Application is running");
// Expose metrics, enable health check
break;
case REBALANCING:
logger.warn("Rebalancing in progress");
// May affect latency, alert if frequent
break;
case ERROR:
logger.error("Application in error state");
// Alert immediately
break;
default:
break;
}
});
streams.start();
Health Check Endpoint
@GetMapping("/health")
public ResponseEntity<HealthStatus> health() {
State state = streams.state();
if (state == State.RUNNING) {
return ResponseEntity.ok(new HealthStatus("healthy", state.name()));
} else if (state == State.REBALANCING) {
return ResponseEntity.status(503)
.body(new HealthStatus("degraded", state.name()));
} else {
return ResponseEntity.status(503)
.body(new HealthStatus("unhealthy", state.name()));
}
}
3. JMX Metrics
Enabling JMX
JMX enabled by default
Java options:
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9999
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
Query via:
├─ JConsole
├─ VisualVM
├─ Prometheus JMX Exporter
└─ Custom monitoring tools
Metric Categories
kafka.streams:
├─ type=stream-metrics (application-level)
├─ type=stream-thread-metrics (per thread)
├─ type=stream-task-metrics (per task)
├─ type=stream-processor-node-metrics (per node)
└─ type=stream-state-metrics (per state store)
4. Application-Level Metrics
Key Metrics
# Application state
kafka.streams:type=stream-metrics,
client-id=*,
name=state
# Number of threads
kafka.streams:type=stream-metrics,
client-id=*,
name=alive-stream-threads
# Failed threads
kafka.streams:type=stream-metrics,
client-id=*,
name=failed-stream-threads
# Processing rate (records/sec)
kafka.streams:type=stream-metrics,
client-id=*,
name=process-rate
Example: Querying JMX
// Programmatically access metrics
KafkaStreams streams = new KafkaStreams(topology, props);
streams.start();
Map<MetricName, ? extends Metric> metrics = streams.metrics();
for (Map.Entry<MetricName, ? extends Metric> entry : metrics.entrySet()) {
MetricName name = entry.getKey();
Metric metric = entry.getValue();
if (name.name().equals("process-rate")) {
System.out.println("Process rate: " + metric.metricValue());
}
}
5. Thread-Level Metrics
Key Metrics
# Thread state (running, dead, etc.)
kafka.streams:type=stream-thread-metrics,
thread-id=*,
name=thread-state
# Commit latency (ms)
kafka.streams:type=stream-thread-metrics,
thread-id=*,
name=commit-latency-avg
# Commit rate (commits/sec)
kafka.streams:type=stream-thread-metrics,
thread-id=*,
name=commit-rate
# Poll latency (ms)
kafka.streams:type=stream-thread-metrics,
thread-id=*,
name=poll-latency-avg
Monitoring Thread Health
Alert if:
├─ thread-state != "RUNNING" (for extended period)
├─ commit-latency-avg > threshold (e.g., 5 seconds)
├─ poll-latency-avg > threshold (e.g., 2 seconds)
└─ commit-rate drops to 0 (no progress)
6. Task-Level Metrics
Key Metrics
# Records processed per task
kafka.streams:type=stream-task-metrics,
task-id=*,
name=process-rate
# Records lag (behind latest offset)
kafka.streams:type=stream-task-metrics,
task-id=*,
name=consumer-lag-max
# Restore progress (state restoration)
kafka.streams:type=stream-task-metrics,
task-id=*,
name=restore-consumer-records-read-total
# Enforced processing (suppression)
kafka.streams:type=stream-task-metrics,
task-id=*,
name=suppression-buffer-size-current
Consumer Lag
Consumer lag = how far behind the latest offset
Healthy:
└─ Lag = 0 or small (< 1000 records)
Unhealthy:
└─ Lag growing (falling behind)
Causes:
├─ Slow processing
├─ Insufficient resources
├─ Rebalancing
└─ Downstream bottleneck
Alert if:
└─ Lag > threshold for X minutes
7. State Store Metrics
Key Metrics
# Number of entries in state store
kafka.streams:type=stream-state-metrics,
task-id=*,
store-id=*,
name=num-entries
# State store size (bytes)
kafka.streams:type=stream-state-metrics,
task-id=*,
store-id=*,
name=size-in-bytes
# Restore lag (during restoration)
kafka.streams:type=stream-task-metrics,
task-id=*,
name=restore-consumer-records-lag-max
Monitoring State Growth
Track:
├─ num-entries over time
├─ size-in-bytes over time
Alert if:
├─ Unbounded growth (memory leak?)
├─ Approaching disk/memory limits
└─ Restore taking too long (large state)
8. Rebalance Detection
Rebalance Metrics
# Rebalance start
kafka.streams:type=stream-thread-metrics,
thread-id=*,
name=rebalance-latency-total
# Rebalance duration (ms)
kafka.streams:type=stream-thread-metrics,
thread-id=*,
name=last-rebalance-seconds-ago
# Time since last rebalance
kafka.streams:type=stream-thread-metrics,
thread-id=*,
name=time-since-last-rebalance-ms
Detecting Frequent Rebalances
Problem: Rebalances too frequent
Causes:
├─ max.poll.interval.ms too low
├─ session.timeout.ms too low
├─ Instances joining/leaving frequently
└─ Network instability
Detection:
└─ Count rebalances per hour
Alert if:
└─ > 10 rebalances/hour (indicates instability)
Rebalance Listener
streams.setGlobalStateRestoreListener(new StateRestoreListener() {
@Override
public void onRestoreStart(TopicPartition topicPartition, String storeName,
long startingOffset, long endingOffset) {
logger.info("Restore started: store={}, partition={}, records={}",
storeName, topicPartition, endingOffset - startingOffset);
}
@Override
public void onBatchRestored(TopicPartition topicPartition, String storeName,
long batchEndOffset, long numRestored) {
logger.debug("Batch restored: {}", numRestored);
}
@Override
public void onRestoreEnd(TopicPartition topicPartition, String storeName,
long totalRestored) {
logger.info("Restore complete: store={}, total={}", storeName, totalRestored);
}
});
9. Performance Monitoring
Throughput Metrics
# Records processed per second
kafka.streams:type=stream-metrics,
client-id=*,
name=process-rate
# Records produced per second
kafka.streams:type=stream-metrics,
client-id=*,
name=produce-rate
# Records consumed per second
kafka.streams:type=stream-metrics,
client-id=*,
name=consume-rate
Latency Metrics
# End-to-end latency (input to output)
kafka.streams:type=stream-processor-node-metrics,
task-id=*,
processor-node-id=*,
name=process-latency-avg
# Commit latency
kafka.streams:type=stream-thread-metrics,
thread-id=*,
name=commit-latency-avg
Resource Utilization
Monitor at OS level:
CPU:
└─ Track CPU % per instance
Memory:
├─ Heap usage (JVM)
├─ Off-heap (RocksDB)
└─ Total memory
Disk:
├─ Disk usage (state stores)
├─ Disk I/O (read/write)
└─ IOPS
Network:
├─ Bandwidth (in/out)
└─ Connections
10. Alerting Strategy
Critical Alerts (Page Immediately)
1. Application state = ERROR
└─ Application cannot recover
2. All threads dead
└─ No processing happening
3. Consumer lag growing rapidly
└─ Falling behind, data loss risk
4. Disk space < 10%
└─ State stores at risk
5. Failed stream threads > 0
└─ Threads crashing
Warning Alerts (Investigate)
1. Application state = REBALANCING (> 5 minutes)
└─ Long rebalance, may affect SLA
2. Consumer lag > threshold (e.g., 10000 records)
└─ Falling behind, needs attention
3. Commit latency > 5 seconds
└─ Slow commits, may indicate issue
4. Restore taking > 30 minutes
└─ Large state or slow network
5. Frequent rebalances (> 10/hour)
└─ Instability, investigate
Informational Alerts (FYI)
1. Application state = RUNNING
└─ Healthy, processing normally
2. State restore started
└─ Expected on startup/rebalance
3. Consumer lag = 0
└─ Fully caught up
11. Dashboards
Grafana Dashboard Example
Panels:
1. Application State
└─ Gauge: state (RUNNING, REBALANCING, ERROR)
2. Throughput
├─ Line chart: process-rate
├─ Line chart: consume-rate
└─ Line chart: produce-rate
3. Consumer Lag
└─ Line chart: consumer-lag-max (per task)
4. Thread Health
├─ Gauge: alive-stream-threads
└─ Gauge: failed-stream-threads
5. Commit Metrics
├─ Line chart: commit-latency-avg
└─ Line chart: commit-rate
6. State Store Size
├─ Line chart: num-entries
└─ Line chart: size-in-bytes
7. Rebalances
└─ Counter: rebalance count per hour
8. Resource Usage
├─ Line chart: CPU %
├─ Line chart: Memory %
└─ Line chart: Disk %
12. Logging Best Practices
Structured Logging
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(MyApp.class);
// Structured logging with context
logger.info("Processing record: key={}, topic={}, partition={}, offset={}",
record.key(), context.topic(), context.partition(), context.offset());
logger.error("Processing failed: key={}, error={}",
record.key(), exception.getMessage(), exception);
Log Levels
ERROR:
├─ Unrecoverable errors
├─ Application crashes
└─ State corruption
WARN:
├─ Recoverable errors
├─ Retries
├─ Degraded performance
└─ Frequent rebalances
INFO:
├─ Application lifecycle (start, stop, rebalance)
├─ State transitions
└─ Configuration
DEBUG:
├─ Record-level processing
├─ State store operations
└─ Internal details (development only)
Log Aggregation
Use centralized logging:
├─ ELK Stack (Elasticsearch, Logstash, Kibana)
├─ Splunk
├─ Datadog
└─ CloudWatch (AWS)
Benefits:
├─ Search across all instances
├─ Correlate events
├─ Alert on log patterns
└─ Long-term retention
13. Troubleshooting Common Issues
Issue 1: High Consumer Lag
Symptom: Lag growing continuously
Causes:
├─ Processing too slow (bottleneck)
├─ Insufficient parallelism (too few tasks/instances)
├─ Downstream service slow (external calls)
└─ Resource constraints (CPU, memory)
Solutions:
1. Optimize processing logic
2. Scale horizontally (more instances)
3. Increase partitions (more parallelism)
4. Optimize external calls (caching, batching)
5. Add more CPU/memory
Issue 2: Frequent Rebalances
Symptom: Rebalances every few minutes
Causes:
├─ max.poll.interval.ms too low
├─ Processing too slow
├─ Instances flapping (crash loops)
└─ Network instability
Solutions:
1. Increase max.poll.interval.ms
2. Optimize processing (faster polling)
3. Fix application crashes
4. Stabilize network
5. Use static membership (group.instance.id)
Issue 3: Application Stuck in REBALANCING
Symptom: Rebalancing for extended period
Causes:
├─ Large state restoration (hours)
├─ Standby replicas not configured
├─ Slow network to Kafka
└─ Insufficient broker resources
Solutions:
1. Use standby replicas (fast failover)
2. Optimize restore consumer config
3. Improve network bandwidth
4. Scale Kafka brokers
5. Reduce state store size (TTL, compaction)
Issue 4: High Memory Usage
Symptom: OOM errors or high heap usage
Causes:
├─ Large state stores (in-memory)
├─ Large cache (cache.max.bytes.buffering)
├─ Too many tasks per instance
└─ Memory leak (application code)
Solutions:
1. Use RocksDB (off-heap)
2. Reduce cache size
3. Scale horizontally (more instances)
4. Profile for memory leaks
5. Increase heap size (if needed)
Issue 5: Slow Processing
Symptom: Low throughput, high latency
Causes:
├─ Blocking I/O (external calls)
├─ Large batch sizes
├─ Slow state store (disk I/O)
└─ Inefficient processing logic
Solutions:
1. Async external calls
2. Optimize batch sizes
3. Use SSD for state stores
4. Optimize processing logic
5. Profile CPU usage
14. Capacity Planning
Estimating Resources
Factors:
1. Input rate (records/sec)
└─ Determines throughput needs
2. State size (GB)
└─ Determines disk/memory needs
3. Processing complexity
└─ Determines CPU needs
4. Partition count
└─ Determines max parallelism
5. Latency requirements
└─ Determines commit interval
Sizing Guidelines
SMALL WORKLOAD (< 10K records/sec):
├─ 2-4 instances
├─ 2-4 threads per instance
├─ 4 GB heap
├─ 50 GB disk (state stores)
└─ 4 vCPUs
MEDIUM WORKLOAD (10K-100K records/sec):
├─ 5-10 instances
├─ 4-8 threads per instance
├─ 8 GB heap
├─ 100-500 GB disk
└─ 8 vCPUs
LARGE WORKLOAD (> 100K records/sec):
├─ 10+ instances
├─ 8+ threads per instance
├─ 16 GB heap
├─ 500+ GB disk (SSD)
└─ 16+ vCPUs
15. Operational Best Practices
1. Monitor Application State
// Always set state listener
streams.setStateListener((newState, oldState) -> {
logger.info("State: {} -> {}", oldState, newState);
metricsRegistry.recordStateChange(newState);
});
2. Configure Standby Replicas
// For production, use standby replicas
props.put(StreamsConfig.NUM_STANDBY_REPLICAS_CONFIG, 1);
// Fast failover, minimal downtime
3. Use Graceful Shutdown
// Always close gracefully
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
streams.close(Duration.ofSeconds(30));
}));
4. Set Uncaught Exception Handler
// Handle unexpected errors
streams.setUncaughtExceptionHandler((thread, throwable) -> {
logger.error("Uncaught exception: {}", throwable.getMessage(), throwable);
return StreamThreadExceptionResponse.SHUTDOWN_APPLICATION;
});
5. Enable Metrics Export
Use Prometheus JMX Exporter or similar:
1. Add JMX exporter agent
2. Export to Prometheus
3. Visualize in Grafana
4. Set up alerts
Example:
java -javaagent:jmx_exporter.jar=9090:config.yaml -jar my-app.jar
6. Test in Staging
Before production:
├─ Load test (simulate production load)
├─ Failure test (kill instances, broker failures)
├─ Rebalance test (add/remove instances)
└─ Restore test (measure restoration time)
Verify:
├─ Throughput meets requirements
├─ Latency within SLA
├─ Recovery time acceptable
└─ Resource usage reasonable
16. Mental Model Summary
Monitoring Layers
┌───────────────────────────────────────────────┐
│ APPLICATION STATE │
│ └─ RUNNING, REBALANCING, ERROR │
└───────────────┬───────────────────────────────┘
↓
┌───────────────▼───────────────────────────────┐
│ THREAD METRICS │
│ └─ Alive threads, commit latency, poll rate │
└───────────────┬───────────────────────────────┘
↓
┌───────────────▼───────────────────────────────┐
│ TASK METRICS │
│ └─ Process rate, consumer lag, restore │
└───────────────┬───────────────────────────────┘
↓
┌───────────────▼───────────────────────────────┐
│ STATE STORE METRICS │
│ └─ Size, entries, restore lag │
└───────────────────────────────────────────────┘
Monitor from top to bottom
Key Metrics Summary
HEALTH:
├─ Application state (RUNNING)
├─ Alive threads (> 0)
└─ Failed threads (= 0)
THROUGHPUT:
├─ Process rate (records/sec)
├─ Consume rate (records/sec)
└─ Produce rate (records/sec)
LAG:
├─ Consumer lag (< threshold)
└─ Growing? Alert!
LATENCY:
├─ Commit latency (ms)
├─ Processing latency (ms)
└─ Poll latency (ms)
STATE:
├─ State store size (entries, bytes)
└─ Restore progress (%)
17. Questions You Should Be Able to Answer
After this module, you should be able to answer:
✅ What are the KafkaStreams application states?
✅ What are the most important metrics to monitor?
✅ How do I detect consumer lag?
✅ How do I detect frequent rebalances?
✅ What JMX metrics should I track?
✅ When should I alert vs just log?
✅ How do I troubleshoot high consumer lag?
✅ How do I estimate resource requirements?
18. What's Next?
Now that you understand monitoring and operations, the final module will cover:
- Module 13.1: Advanced configuration & tool selection — performance tuning, when to use Kafka Streams vs alternatives
Summary
You learned:
- ✅ Monitor application state (RUNNING, REBALANCING, ERROR) with state listener
- ✅ Key JMX metrics: process-rate, consumer-lag-max, commit-latency-avg
- ✅ Consumer lag indicates if falling behind (alert if growing)
- ✅ Detect frequent rebalances (> 10/hour indicates instability)
- ✅ Track thread health (alive threads, failed threads)
- ✅ Monitor state store size (entries, bytes) for growth
- ✅ Set up critical alerts (ERROR state, high lag, disk space)
- ✅ Use graceful shutdown, standby replicas, and exception handlers
- ✅ Troubleshoot common issues: lag, rebalances, OOM, slow processing
- ✅ Capacity planning based on input rate, state size, and latency requirements
Next up: Module 13.1 — Advanced Configuration & Choosing the Right Tool — Final module covering performance tuning and when to use Kafka Streams.