6.3 Restore & Recovery
Module Overview
Goal: Understand state restoration when instances fail or tasks migrate.
What You'll Learn:
- When restoration happens
- How state is restored from changelogs
- Restore phases and timing
- Monitoring restore progress
- Optimizing restore performance
- Handling restore failures
What's NOT Covered Here:
- State store internals (covered in Module 6.1)
- Changelog mechanics (covered in Module 6.2)
- Standby replicas (covered in Module 6.2)
Key Insight: State restoration is automatic but can be slow for large state — understanding the process helps you optimize recovery time.
1. When Does Restoration Happen?
Cold Start
Scenario: Application starts for the first time
├─ No local state exists
├─ Must restore from changelog
└─ Happens once per instance
Duration: Depends on changelog size
Task Reassignment
Scenario: Task moves to different instance
Causes:
├─ Rebalance (new instance added/removed)
├─ Instance crash/restart
├─ Manual scale-up/scale-down
└─ Partition reassignment
New instance must restore task's state
State Corruption
Scenario: Local state invalid or corrupted
Causes:
├─ Disk failure
├─ Process killed mid-write
├─ RocksDB corruption
└─ Manual state deletion
Kafka Streams detects corruption → triggers restore
Manual State Cleanup
# Delete local state directory
rm -rf /tmp/kafka-streams/my-app/0_0
# On restart, state restored from changelog
2. The Restoration Process
High-Level Flow
1. Instance assigned task
↓
2. Check local state directory
↓
3. If no state (or corrupted):
├─ Enter RESTORING state
├─ Create restore consumer
├─ Read changelog topic from beginning
├─ Rebuild state store
└─ Enter RUNNING state
↓
4. Resume processing input records
Visual Representation
┌──────────────────────────────────────────────┐
│ TASK ASSIGNMENT │
│ ├─ Instance 2 assigned Task 0 │
│ └─ No local state exists │
└──────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────┐
│ ENTER RESTORING STATE │
│ ├─ Create restore consumer │
│ ├─ Subscribe to changelog: │
│ │ my-app-user-counts-changelog │
│ └─ Seek to beginning │
└──────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────┐
│ READ CHANGELOG RECORDS │
│ ├─ Fetch records in batches │
│ ├─ Write to local state store (RocksDB) │
│ └─ Track progress │
└──────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────┐
│ RESTORATION COMPLETE │
│ ├─ State store fully rebuilt │
│ ├─ Close restore consumer │
│ └─ Enter RUNNING state │
└──────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────┐
│ RESUME PROCESSING │
│ └─ Process input records normally │
└──────────────────────────────────────────────┘
3. Restore Consumer
What is the Restore Consumer?
Restore consumer = internal consumer for reading changelogs
NOT the same as:
├─ Main consumer (reads input topics)
└─ Standby consumer (keeps standby replicas up-to-date)
Purpose:
└─ Read changelog topic to rebuild state
Configuration
// Restore consumer configs
props.put(StreamsConfig.RESTORE_CONSUMER_PREFIX +
ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 1000);
props.put(StreamsConfig.RESTORE_CONSUMER_PREFIX +
ConsumerConfig.FETCH_MAX_BYTES_CONFIG, 52428800); // 50 MB
// Prefix all restore consumer configs with:
// "restore.consumer."
Restore vs Main Consumer
MAIN CONSUMER:
├─ Reads input topics
├─ Processes records one-by-one
└─ Commits offsets
RESTORE CONSUMER:
├─ Reads changelog topics
├─ Bulk loads state store
└─ No offset commits (reads entire changelog)
4. Restoration Phases
Phase 1: Discovery
Task assigned:
├─ Check if local state exists
├─ Compare local offsets with changelog
└─ Determine if restore needed
Decision:
├─ Local state up-to-date → skip restore
└─ Local state missing/stale → start restore
Phase 2: Initialization
Prepare for restore:
├─ Create restore consumer
├─ Subscribe to changelog topic
├─ Create/clear local state directory
└─ Initialize state store
Phase 3: Bulk Load
Read changelog in batches:
├─ Fetch records (default: 1000/poll)
├─ Write to state store
├─ Track progress
└─ Repeat until end of changelog
This is the slowest phase
Phase 4: Catchup
Near end of changelog:
├─ Catch up to latest offset
├─ Apply remaining updates
└─ Ensure state fully restored
Phase 5: Completion
Restoration complete:
├─ Close restore consumer
├─ Mark task as RUNNING
└─ Resume normal processing
5. Restore Time Estimation
Factors Affecting Restore Time
1. Changelog size (records/bytes)
└─ Larger state = longer restore
2. Network bandwidth
└─ Fetching changelog from Kafka
3. Disk I/O
└─ Writing to RocksDB
4. State store type
└─ RocksDB vs in-memory
5. Restore consumer config
└─ fetch.max.bytes, max.poll.records
6. Broker load
└─ Broker response time
Typical Restore Times
Small state (< 1 GB):
└─ 1-5 minutes
Medium state (1-10 GB):
└─ 5-20 minutes
Large state (10-100 GB):
└─ 20-60 minutes
Very large state (> 100 GB):
└─ Hours
Formula (Rough Estimate)
Restore time ≈ (Changelog size in bytes) / (Network throughput)
Example:
├─ Changelog: 10 GB
├─ Network: 100 MB/s
└─ Time: 10 GB / 100 MB/s = 100 seconds ≈ 2 minutes
(Add 20-50% overhead for writes)
6. Monitoring Restoration
Task State
Task states:
├─ CREATED: Task assigned, not started
├─ RESTORING: Restoring state from changelog
├─ RUNNING: Processing input records
└─ SUSPENDED: Temporarily paused (rebalance)
Monitor state transitions:
kafka.streams:type=stream-thread-metrics,
thread-id=*,
task-id=*,
name=task-restoring-count
Restore Progress Metrics
# Records read from changelog
kafka.streams:type=stream-task-metrics,
task-id=*,
name=restore-consumer-records-read-total
# Bytes read from changelog
kafka.streams:type=stream-task-metrics,
task-id=*,
name=restore-consumer-bytes-read-total
# Restore lag (records behind)
kafka.streams:type=stream-task-metrics,
task-id=*,
name=restore-consumer-records-lag-max
# Restore rate
kafka.streams:type=stream-task-metrics,
task-id=*,
name=restore-consumer-records-read-rate
Logging Restore Progress
Kafka Streams logs:
INFO Starting restore for task 0_0 (org.apache.kafka.streams.processor.internals.Task)
INFO Restoring state for store user-counts from beginning
INFO Restored 100000 records for store user-counts (10% complete)
INFO Restored 500000 records for store user-counts (50% complete)
INFO Restoration complete for task 0_0
Custom Monitoring
// StateRestoreListener to track progress
props.put(StreamsConfig.STATE_DIR_CONFIG, "/tmp/kafka-streams");
KafkaStreams streams = new KafkaStreams(topology, props);
streams.setGlobalStateRestoreListener(new StateRestoreListener() {
@Override
public void onRestoreStart(TopicPartition topicPartition,
String storeName,
long startingOffset,
long endingOffset) {
System.out.println("Restore started: " + storeName);
System.out.println("Records to restore: " +
(endingOffset - startingOffset));
}
@Override
public void onBatchRestored(TopicPartition topicPartition,
String storeName,
long batchEndOffset,
long numRestored) {
System.out.println("Restored batch: " + numRestored + " records");
}
@Override
public void onRestoreEnd(TopicPartition topicPartition,
String storeName,
long totalRestored) {
System.out.println("Restore complete: " + storeName);
System.out.println("Total restored: " + totalRestored);
}
});
streams.start();
7. Optimizing Restore Performance
1. Increase Fetch Size
// Fetch more data per poll
props.put(StreamsConfig.RESTORE_CONSUMER_PREFIX +
ConsumerConfig.FETCH_MAX_BYTES_CONFIG, 104857600); // 100 MB
props.put(StreamsConfig.RESTORE_CONSUMER_PREFIX +
ConsumerConfig.MAX_PARTITION_FETCH_BYTES_CONFIG, 10485760); // 10 MB
2. Increase Batch Size
// Process more records per batch
props.put(StreamsConfig.RESTORE_CONSUMER_PREFIX +
ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 5000);
3. Use Standby Replicas
// Fast failover without full restore
props.put(StreamsConfig.NUM_STANDBY_REPLICAS_CONFIG, 1);
// Standby already has state → promotes immediately
// No restore needed!
4. Enable Caching
// Fewer changelog records to restore
props.put(StreamsConfig.CACHE_MAX_BYTES_BUFFERING_CONFIG,
10 * 1024 * 1024); // 10 MB
// Caching reduces changelog size → faster restore
5. Tune RocksDB
// RocksDB configuration for faster bulk loading
RocksDBConfigSetter configSetter = new RocksDBConfigSetter() {
@Override
public void setConfig(String storeName, Options options, Map<String, Object> configs) {
// Optimize for bulk writes during restore
options.setMaxWriteBufferNumber(4);
options.setWriteBufferSize(64 * 1024 * 1024); // 64 MB
options.setDisableAutoCompactions(true); // Disable during restore
}
};
props.put(StreamsConfig.ROCKSDB_CONFIG_SETTER_CLASS_CONFIG,
configSetter.getClass());
6. Network Optimization
Ensure low latency to Kafka brokers:
├─ Co-locate Streams app and brokers (same data center)
├─ Use high-bandwidth network (10 Gbps+)
└─ Avoid cross-region restoration
8. Standby Replicas vs Full Restore
Restore Without Standby
Failover scenario:
Instance 1 crashes:
├─ Task reassigned to Instance 2
├─ Instance 2 has no state
├─ Full restore from changelog (20 minutes)
└─ Application unavailable for 20 minutes
User experience: Poor
Restore With Standby
Failover scenario:
Instance 1 crashes:
├─ Task reassigned to Instance 2 (standby)
├─ Instance 2 already has state
├─ Minor catchup (< 1 minute)
└─ Application available quickly
User experience: Good
Standby Configuration
// Enable standby replicas
props.put(StreamsConfig.NUM_STANDBY_REPLICAS_CONFIG, 1);
Cost:
├─ 2× storage (primary + standby)
├─ 2× instances (minimum)
└─ More network I/O
Benefit:
├─ Fast failover
└─ High availability
9. Handling Restore Failures
Failure Scenarios
1. Changelog topic deleted
└─ State lost permanently
2. Insufficient disk space
└─ Restore fails mid-process
3. RocksDB corruption during restore
└─ Retry from beginning
4. Network partition
└─ Restore stalled
Automatic Retry
Kafka Streams retries restore automatically:
On failure:
├─ Log error
├─ Clean up partial state
├─ Retry restore from beginning
└─ Continue retrying indefinitely
Manual intervention needed if:
├─ Changelog deleted
├─ Insufficient resources
└─ Configuration issue
Handling Missing Changelog
Scenario: Changelog topic deleted manually
Result:
├─ Restore fails (topic not found)
├─ Application cannot start
└─ State lost permanently
Solution:
1. Reset application (lose state):
kafka-streams-application-reset.sh \
--application-id my-app \
--bootstrap-servers localhost:9092
2. Reprocess from beginning (rebuild state):
├─ Delete local state
├─ Application restores from input topics
└─ Slow but recovers state
Insufficient Disk Space
Symptom:
├─ Restore fails mid-process
└─ Error: "No space left on device"
Solution:
1. Free up disk space
2. Increase disk capacity
3. Delete old state stores
4. Reduce state store size (TTL, compaction)
10. Restore During Rebalance
What Happens?
Rebalance triggered:
├─ Tasks suspended (SUSPENDED state)
├─ Some tasks reassigned
└─ New instance restores assigned tasks
During restore:
├─ Processing stalled
├─ Consumer not polling
└─ Application partially unavailable
Minimizing Rebalance Impact
1. Use cooperative rebalancing (default in recent versions)
└─ Only affected tasks stopped
2. Use standby replicas
└─ Fast promotion, minimal restore
3. Avoid frequent rebalances
├─ Set higher session.timeout.ms
└─ Stable cluster (no frequent scaling)
11. State Directory Management
Default State Directory
Default location:
/tmp/kafka-streams/{application.id}/{task.id}
Example:
/tmp/kafka-streams/my-app/0_0/rocksdb/user-counts
Problems with /tmp:
├─ Cleared on reboot (full restore needed)
└─ Limited space
Custom State Directory
// Use dedicated disk with more space
props.put(StreamsConfig.STATE_DIR_CONFIG,
"/var/kafka-streams-state");
Benefits:
├─ Survives reboots
├─ More disk space
└─ Better performance (SSD)
Cleaning Up State
# Safe: Delete state for specific task
rm -rf /var/kafka-streams-state/my-app/0_0
# Dangerous: Delete all state
rm -rf /var/kafka-streams-state/my-app
# On restart, all state restored from changelog
12. Restore vs Reprocessing
Restore (from Changelog)
Source: Changelog topic
Process:
├─ Read compacted changelog
├─ Only latest value per key
└─ Fast (bulk load)
Use case:
└─ Normal failover/reassignment
Reprocessing (from Input)
Source: Input topic(s)
Process:
├─ Read all input records from beginning
├─ Recompute all aggregations
└─ Slow (full processing)
Use case:
├─ Changelog deleted
├─ Application reset
└─ Logic change (reprocessing needed)
Comparison
RESTORE (CHANGELOG):
✅ Fast (minutes)
✅ Automatic
✅ No logic execution
❌ Requires changelog
REPROCESSING (INPUT):
✅ Always possible
✅ Rebuilds lost changelogs
❌ Slow (hours/days)
❌ Must replay all input
13. Restore Best Practices
1. Use Standby Replicas for Critical Apps
// Production configuration
props.put(StreamsConfig.NUM_STANDBY_REPLICAS_CONFIG, 1);
// Balance cost vs availability
2. Monitor Restore Progress
// Implement StateRestoreListener
streams.setGlobalStateRestoreListener(new MyRestoreListener());
// Log progress, send metrics to monitoring system
3. Optimize Restore Consumer Config
// Increase throughput
props.put(StreamsConfig.RESTORE_CONSUMER_PREFIX +
ConsumerConfig.FETCH_MAX_BYTES_CONFIG, 104857600);
props.put(StreamsConfig.RESTORE_CONSUMER_PREFIX +
ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 5000);
4. Use Dedicated State Directory
// Avoid /tmp
props.put(StreamsConfig.STATE_DIR_CONFIG,
"/var/kafka-streams-state");
// Use SSD for better performance
4.5 Same-Host Restart Advantage (Cached State Store)
Key Optimization: When a Kafka Streams instance restarts on the same host, it can reuse existing local state, avoiding full restoration from the changelog!
How It Works:
SCENARIO 1: Restart on Same Host
────────────────────────────────────
1. Instance stops gracefully
└─ Local state remains at /var/kafka-streams-state/
2. Instance restarts on same host
├─ Check local state directory
├─ Find existing state for Task 0
├─ Validate state is valid
└─ Check offset vs changelog
3. Delta Catch-Up (Only New Records)
├─ Local offset: 1,000,000
├─ Changelog offset: 1,000,500
└─ Read only 500 records (delta)
4. Resume Processing
└─ Total downtime: Seconds!
SCENARIO 2: Restart on Different Host
────────────────────────────────────
1. Instance stops
2. Task reassigned to different host
└─ No local state exists
3. Full Restoration
├─ Read entire changelog (1,000,500 records)
└─ Rebuild state from scratch
4. Resume Processing
└─ Total downtime: Minutes!
Performance Comparison:
| Scenario | State Size | Restoration Time | Network I/O |
|---|---|---|---|
| Same host | 10 GB | 5-15 seconds | Only delta (~MB) |
| Different host | 10 GB | 2-5 minutes | Full state (10 GB) |
Best Practices for Same-Host Advantage:
1. Use Persistent Disks (Not Ephemeral)
├─ Docker: Mount volume to /var/kafka-streams-state
├─ Kubernetes: Use PersistentVolumeClaim
└─ Avoid /tmp (cleared on restart)
2. Configure Pod/Container Affinity
├─ Kubernetes: Use StatefulSets (not Deployments)
├─ Tie instance to specific node
└─ Ensures same-host restart
3. Set Dedicated State Directory
props.put(StreamsConfig.STATE_DIR_CONFIG,
"/persistent-disk/kafka-streams-state");
4. Graceful Shutdown
├─ Call streams.close() properly
├─ Allows state to flush cleanly
└─ State directory remains valid
Soccer Team Analogy:
Think of state restoration like substituting players:
SAME-HOST RESTART (Player returns after timeout):
├─ Player (task) leaves field temporarily
├─ Player comes back to same position
├─ Player remembers game state (score, strategy)
└─ Resumes immediately (no briefing needed)
DIFFERENT-HOST RESTART (New player substituted in):
├─ New player enters field
├─ Doesn't know game state (score, strategy)
├─ Coach must brief player (restore from changelog)
└─ Takes time before player is effective
Kubernetes Example (StatefulSet):
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: kafka-streams-app
spec:
serviceName: kafka-streams
replicas: 3
template:
spec:
containers:
- name: streams-app
image: my-streams-app:1.0
volumeMounts:
- name: state-store
mountPath: /var/kafka-streams-state
volumeClaimTemplates:
- metadata:
name: state-store
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 50Gi # Persistent disk for state
Key Takeaway:
When possible, restart instances on the same host to leverage cached local state. This reduces restoration time from minutes to seconds and significantly improves availability during rolling restarts or failure recovery.
5. Enable Caching
// Reduce changelog size
props.put(StreamsConfig.CACHE_MAX_BYTES_BUFFERING_CONFIG,
10 * 1024 * 1024);
// Faster restore (fewer records)
6. Never Delete Changelog Topics Manually
NEVER:
kafka-topics.sh --delete --topic my-app-user-counts-changelog
If deleted:
├─ State lost permanently (if no local state)
└─ Must reprocess from input topics
7. Plan for Restore Time
Expect restore time in:
├─ Cold starts (first deployment)
├─ Scaling events (new instances)
└─ Failover (without standby)
Capacity planning:
└─ Ensure instances can handle load during restore
14. Troubleshooting Restore Issues
Symptom: Restore Taking Too Long
Causes:
├─ Large changelog (100+ GB)
├─ Slow network
├─ Low fetch settings
└─ Broker under load
Solutions:
1. Increase restore consumer fetch configs
2. Use standby replicas
3. Scale Kafka brokers
4. Check network bandwidth
Symptom: Restore Stuck at 99%
Cause: Waiting for final records
Changelog compaction not complete:
├─ Old records still present
└─ Must read to end
Solution: Wait (usually resolves in minutes)
Symptom: Restore Keeps Failing
Logs:
ERROR Failed to restore state for task 0_0
Causes:
├─ RocksDB corruption
├─ Disk space issue
├─ Configuration problem
└─ Changelog topic issue
Solutions:
1. Check disk space
2. Check RocksDB logs
3. Verify changelog topic exists
4. Delete corrupted local state
15. Mental Model Summary
The Restore Lifecycle
┌──────────────────────────────────────┐
│ TASK ASSIGNED │
│ └─ Check local state │
└──────────────────────────────────────┘
↓
[State exists?]
↙ ↘
YES NO
↓ ↓
[SKIP RESTORE] [START RESTORE]
↓ ↓
↓ ┌───────────────────┐
↓ │ RESTORING │
↓ │ ├─ Read changelog │
↓ │ ├─ Rebuild state │
↓ │ └─ Track progress │
↓ └───────────────────┘
↓ ↓
└──────┬─────┘
↓
┌───────────────┐
│ RUNNING │
│ └─ Process │
└───────────────┘
Fast Restore vs Slow Restore
FAST RESTORE (Standby):
├─ State already present
├─ Minor catchup (~seconds)
└─ High availability
SLOW RESTORE (From Changelog):
├─ Read entire changelog
├─ Rebuild state (~minutes)
└─ Application unavailable
16. Questions You Should Be Able to Answer
After this module, you should be able to answer:
✅ When does state restoration happen?
✅ What is the restore consumer?
✅ What are the phases of restoration?
✅ How long does restore typically take?
✅ How do I monitor restore progress?
✅ How can I optimize restore performance?
✅ What's the benefit of standby replicas for restore?
✅ What happens if the changelog topic is deleted?
17. What's Next?
Now that you understand state restoration, the next module will cover:
- Module 7.1: Topology design & internal topics — understanding repartition topics and topology structure
- Module 11.1: Interactive queries — querying state during and after restore
- Module 12.1: Monitoring — observing restore metrics in production
Summary
You learned:
- ✅ State restored from changelog when task reassigned or state missing
- ✅ Restore has 5 phases: discovery, initialization, bulk load, catchup, completion
- ✅ Restore consumer reads changelog to rebuild state
- ✅ Restore time depends on changelog size, network, and disk I/O
- ✅ Standby replicas eliminate full restore (fast failover)
- ✅ Monitor restore with metrics and
StateRestoreListener - ✅ Optimize restore with fetch configs, caching, and RocksDB tuning
- ✅ Never delete changelog topics manually (state lost permanently)
Next up: Module 7.1 — Topology Design & Internal Topics — Learn how Kafka Streams structures processing logic and manages repartition topics.