4.3 Failover Scenarios & Edge Cases
Module Overview
Goal: Explain real failure behavior, edge cases, and data loss boundaries.
What You'll Learn:
- Broker crash scenarios and recovery
- Network partition behavior
- Split-brain prevention
- Data loss scenarios and boundaries
- Cascading failures
- Recovery patterns
What's NOT Covered Here:
- Basic leader election (covered in Module 4.2)
- ISR mechanics (covered in Module 3.2)
- Broker lifecycle (covered in Module 7.1)
Key Insight: Kafka's failover behavior is well-defined but has edge cases. Understanding real failure scenarios helps you design resilient systems and set realistic expectations.
1. Single Broker Failure
Clean Failover (ISR Intact)
SCENARIO:
Initial state:
├─ Replicas: [1, 2, 3]
├─ Leader: 1
└─ ISR: [1, 2, 3]
Event: Broker 1 dies
Failover:
1. Controller detects Broker 1 failure
└─ Session timeout (ZK) or heartbeat miss (KRaft)
2. Controller triggers election:
├─ Remove Broker 1 from ISR → ISR: [2, 3]
├─ Select new leader → Broker 2
└─ Increment epoch
3. Controller sends LeaderAndIsrRequest:
└─ Brokers 2 and 3 updated
4. Broker 2 becomes leader:
├─ Starts accepting writes
└─ HW advances with new ISR
5. Clients:
├─ Receive NOT_LEADER_FOR_PARTITION error
├─ Refresh metadata
└─ Retry to Broker 2 (new leader)
Result:
├─ Brief unavailability (~seconds)
├─ No data loss (ISR had all committed data)
└─ Service restored
Degraded Failover (ISR Shrinks)
SCENARIO:
Initial state:
├─ Replicas: [1, 2, 3]
├─ Leader: 1
└─ ISR: [1, 2] (Broker 3 already lagging)
Event: Broker 1 dies
Failover:
1. Controller removes Broker 1 from ISR:
└─ ISR: [2] (only one ISR member left!)
2. Controller elects Broker 2 as leader
3. Problem: ISR size = 1
├─ If min.insync.replicas = 2:
│ └─ Writes fail with NOT_ENOUGH_REPLICAS
└─ Partition degraded (no write availability)
4. Recovery:
├─ Broker 3 catches up
├─ Rejoins ISR → ISR: [2, 3]
└─ Writes resume
Result:
├─ Write unavailability until ISR recovers
└─ Read availability maintained
2. Multiple Broker Failures
Majority Failure
SCENARIO:
Initial state:
├─ Replicas: [1, 2, 3]
├─ Leader: 1
└─ ISR: [1, 2, 3]
Event: Brokers 1 and 2 die simultaneously
State:
├─ Only Broker 3 alive
├─ ISR: [3] (if Broker 3 was in ISR)
└─ Or ISR: [] (if Broker 3 was out-of-sync)
Scenario A: Broker 3 in ISR
├─ Broker 3 becomes leader
├─ ISR: [3]
├─ Writes succeed (if min.insync.replicas=1)
└─ No data loss
Scenario B: Broker 3 out-of-sync
├─ No ISR members available
├─ If unclean.leader.election.enable=false:
│ └─ Partition offline (no leader)
├─ If unclean.leader.election.enable=true:
│ ├─ Broker 3 elected (unclean)
│ └─ Data loss (uncommitted records on Brokers 1,2 lost)
└─ Trade-off: Availability vs Durability
All Replicas Down
SCENARIO:
Event: All brokers hosting partition replicas die
Result:
├─ Partition completely offline
├─ No leader available
├─ Clients receive LEADER_NOT_AVAILABLE error
└─ Partition unavailable until at least one replica recovers
Recovery:
1. First replica to restart joins
2. Controller elects it as leader
3. ISR: [first replica]
4. Partition available again
5. Other replicas restart:
├─ Join as followers
├─ Catch up via replication
└─ ISR expands
3. Network Partitions
Split-Brain Prevention
KAFKA USES CENTRALIZED CONTROLLER:
Controller-based coordination:
├─ Single controller makes leadership decisions
├─ Only controller can elect leaders
└─ Brokers cannot self-elect
Prevents split-brain:
├─ Network partition isolates brokers
├─ Isolated brokers cannot elect new leaders
├─ Only controller-side partition makes decisions
└─ No dual leadership possible
Network Partition Scenario
SCENARIO: Network Split
Topology:
├─ Controller: Broker 0
├─ Partition replicas: [1, 2, 3]
└─ Leader: Broker 1
Event: Network partitions cluster:
├─ Partition A: Brokers 0, 1 (controller + leader)
└─ Partition B: Brokers 2, 3 (isolated)
Behavior:
Partition A (with controller):
├─ Controller still functional
├─ Detects Brokers 2, 3 as failed (timeout)
├─ Shrinks ISR: [1]
└─ Broker 1 continues as leader
Partition B (without controller):
├─ Brokers 2, 3 isolated
├─ Cannot elect new leader (no controller)
├─ If they were followers: Stop serving partition
└─ Wait for network to heal
Result:
└─ Partition with controller maintains service
└─ Isolated partition becomes unavailable
Controller Itself Isolated
SCENARIO: Controller Isolated
Event: Network partitions controller from rest of cluster
Behavior:
1. Controller loses contact with brokers
2. Brokers detect controller failure
3. Brokers elect new controller (from majority side)
4. New controller takes over leadership decisions
Old controller:
├─ Eventually detects isolation (via ZK/KRaft)
├─ Steps down as controller
└─ Stops making decisions
Result:
└─ Controller failover (new controller elected)
└─ Service continues on majority side
4. Data Loss Scenarios
Uncommitted Data Loss (Expected)
SCENARIO:
1. Producer writes with acks=1:
├─ Leader appends: offset 1000
├─ Leader responds: Success
└─ Followers haven't replicated yet
2. Leader dies before replication
3. New leader elected (doesn't have offset 1000)
Result:
└─ Offset 1000 lost (never replicated)
Why it happens:
└─ acks=1 doesn't wait for replication
└─ Data was not committed (HW didn't advance)
└─ Expected behavior for acks=1
Mitigation:
└─ Use acks=all + min.insync.replicas=2
Committed Data Loss (Unclean Election)
SCENARIO:
1. Producer writes with acks=all:
├─ Leader: offsets 0-1000 (committed)
├─ ISR: [1, 2]
└─ Follower 3: offsets 0-980 (out-of-sync)
2. Brokers 1 and 2 die (all ISR members down)
3. Unclean election (if enabled):
└─ Broker 3 becomes leader
Result:
├─ Offsets 981-1000 lost
├─ Even though they were committed!
└─ Data loss of committed data
Why it happens:
└─ unclean.leader.election.enable=true
└─ Availability prioritized over durability
Mitigation:
└─ unclean.leader.election.enable=false (default)
└─ Accept partition offline rather than data loss
Correlated Failures
SCENARIO: Disk Failure on All Replicas
Event:
├─ Shared storage backend fails (e.g., SAN)
├─ All replicas lose same data
└─ Even ISR members affected
Result:
└─ Data loss even with RF=3, acks=all
└─ Infrastructure failure (beyond Kafka's control)
Mitigation:
├─ Use independent disks (not shared SAN)
├─ Rack awareness (spread replicas across racks)
└─ Multi-DC replication (for disaster recovery)
5. Cascading Failures
ISR Shrink Cascade
SCENARIO:
1. Broker 1 dies:
└─ ISR: [1, 2, 3] → [2, 3]
2. Increased load on Brokers 2, 3:
├─ More partitions to lead
├─ Higher CPU, disk, network usage
└─ Brokers 2, 3 slow down
3. Some followers lag due to overload:
└─ ISR shrinks further: [2, 3] → [2]
4. If Broker 2 also dies:
└─ ISR: [2] → []
└─ Partition offline (if unclean election disabled)
Mitigation:
├─ Provision capacity for failure scenarios
├─ Monitor ISR shrinks (early warning)
└─ Scale cluster before cascading failures
6. Recovery Patterns
Broker Restarts
SCENARIO: Broker Restarts After Failure
1. Broker starts up:
├─ Loads partition logs
├─ Registers with controller
└─ Marks itself available
2. For partitions where broker is follower:
├─ Starts replication from leader
├─ Catches up (fetches missing data)
└─ Rejoins ISR
3. For partitions where broker was leader:
├─ Now follower (new leader elected during downtime)
├─ Fetches from new leader
├─ Detects epoch change (if log diverged)
├─ Truncates divergent data
└─ Replicates from new leader
Result:
└─ Broker rejoins cluster
└─ ISR expands
└─ Leadership may rebalance (preferred leader election)
Disk Recovery
SCENARIO: Disk Failure and Replacement
1. Disk fails (data lost)
2. Replace disk (empty)
3. Broker restarts
Kafka behavior:
├─ Detects missing partition data
├─ Starts replication from scratch
└─ Fetches all data from leader (full sync)
Recovery time:
└─ Depends on partition size and network speed
└─ Large partitions (100s of GB) may take hours
Impact:
└─ Partition under-replicated until recovery complete
└─ Higher risk (fewer replicas available)
Mitigation:
└─ RAID for disk redundancy
└─ Monitor under-replicated partitions
7. Edge Cases
Leader Epoch Wrap-Around (Theoretical)
EDGE CASE:
Leader epoch is 32-bit integer:
└─ Max value: 2,147,483,647
If leader changes 2 billion times:
└─ Epoch wraps around (overflows)
Reality:
└─ Never happens in practice
└─ Would require leader change every second for 68 years
Kafka handles wrap-around gracefully (if it ever occurs)
Simultaneous Controller and Leader Failure
SCENARIO:
Event: Controller broker dies, and it was also leader for partition
Behavior:
1. Brokers detect controller failure
2. Elect new controller
3. New controller detects partition leader failure
4. New controller elects new partition leader
Result:
└─ Both controller and partition leadership recovered
└─ Two failovers happen (controller, then partition)
└─ Brief service interruption (seconds)
8. Broker Reintegration After Failure
When Failed Broker Returns
SCENARIO:
Timeline:
1. Broker 1 crashes (was leading partitions)
2. Controller elects new leaders (Broker 2 takes over)
3. Partitions continue operating with new leader
4. Broker 1 restarts and rejoins cluster
What happens during reintegration?
Step 1: Log Reconciliation
BROKER 1 STARTUP AND RECONCILIATION:
1. Broker 1 loads local logs from disk:
├─ Partition orders-0: LEO 1050, Epoch 1
├─ Last known leader: Self (Broker 1)
└─ Log contains offsets 0-1049
2. Broker 1 registers with controller:
└─ Controller sends LeaderAndIsrRequest:
├─ New leader: Broker 2
├─ New epoch: 2
└─ ISR: [2, 3] (Broker 1 not in ISR yet)
3. Broker 1 realizes it's now a follower:
└─ Connects to new leader (Broker 2)
4. Broker 1 sends OffsetsForLeaderEpochRequest:
└─ "What offset did Epoch 2 start at?"
5. Leader (Broker 2) responds:
└─ "Epoch 2 started at offset 1009"
6. Broker 1 checks local log:
├─ Has: Epochs 0 and 1, up to offset 1049
├─ Realizes: Offsets 1009-1049 from Epoch 1 are divergent
└─ These offsets conflict with Epoch 2 (new leader's writes)
7. Broker 1 truncates divergent data:
├─ Truncates log to offset 1009
├─ Discards offsets 1009-1049 (41 records lost locally)
└─ Updates LEO to 1009
Result:
└─ Broker 1 log now consistent with current leader
└─ Ready to replicate from offset 1009 onward
Step 2: Role Assignment
BROKER BECOMES FOLLOWER:
Controller actions:
1. Sends LeaderAndIsrRequest to Broker 1:
└─ Role: Follower for all previously led partitions
└─ Leader: Broker 2
└─ ISR: [2, 3] (Broker 1 excluded)
2. Broker 1 transitions:
├─ Replica state: OfflineReplica → TruncatingLog → OnlineFetchingReplica
└─ Starts follower fetch loop
Broker 1 responsibilities:
├─ Fetch from new leader (Broker 2)
├─ Replicate new records
├─ Do NOT accept client writes
└─ Stand by for potential future leadership
Step 3: Catch-Up Replication
BROKER 1 CATCHES UP:
Replication loop:
1. Broker 1 sends FetchRequest to Broker 2:
└─ Fetch offset: 1009 (current LEO)
2. Broker 2 (leader) responds:
└─ Records: offsets 1009-2000 (current leader LEO)
└─ High Watermark: 1995
3. Broker 1 appends to local log:
├─ Writes offsets 1009-2000
├─ Updates LEO to 2001
└─ Updates local HW to 1995
4. Repeat:
└─ Broker 1 continues fetching
└─ Gap between Broker 1 LEO and leader LEO narrows
Catch-up speed:
├─ Depends on network bandwidth
├─ Depends on replication rate (replica.fetch.max.bytes)
└─ Typically: Minutes for GBs of data
Step 4: ISR Reintegration
ISR MEMBERSHIP RESTORED:
Condition for rejoining ISR:
└─ Broker 1 LEO caught up AND lag < replica.lag.time.max.ms
Timeline:
1. Broker 1 LEO: 2000, Leader LEO: 2001 (lag: 1 record)
2. Broker 1 last fetch: 1 second ago
3. replica.lag.time.max.ms: 10 seconds
4. Leader determines: Broker 1 is caught up
5. Leader (Broker 2) adds Broker 1 to ISR:
└─ ISR: [2, 3] → [1, 2, 3]
6. Leader sends AlterPartitionRequest to controller:
└─ New ISR: [1, 2, 3]
7. Controller updates metadata:
└─ Partition ISR now includes Broker 1
8. Broker 1 now participates in durability:
└─ acks=all waits for Broker 1 to replicate
Full circle:
└─ Broker 1 rejoined cluster and ISR
└─ Fault tolerance restored
Data Loss Analysis
WHAT WAS LOST:
Unreplicated writes on Broker 1 are permanently lost:
Before crash:
├─ Leader: Broker 1
├─ HW: 1008 (last committed offset)
├─ Broker 1 LEO: 1050 (42 unreplicated records: 1009-1050)
├─ Broker 2 LEO: 1008
└─ Broker 3 LEO: 1007
After crash:
├─ New leader: Broker 2
├─ New HW: 1007 (min of new ISR LEOs: [2,3])
├─ Broker 2 LEO: 1008
└─ Records 1008-1050 lost forever (43 records)
When Broker 1 restarts:
├─ Truncates to offset 1009 (new epoch start)
├─ Offsets 1009-1050 discarded
└─ Never recoverable
WHY THIS IS ACCEPTABLE:
Records 1008-1050 were NOT committed:
├─ HW was 1008 (last committed)
├─ Records 1009-1050 beyond HW (uncommitted)
└─ Never visible to consumers (read_committed)
Producer behavior (with acks=all):
├─ Producers writing 1009-1050 did NOT receive acks
├─ Producer times out or retries
├─ Eventually writes successfully to new leader
└─ Consistent with Kafka's guarantees
Conclusion:
└─ Data loss limited to uncommitted records
└─ No consumer-visible data lost
└─ Durable data (up to HW) preserved
Monitoring Reintegration
TRACKING RECOVERY PROGRESS:
Check partition status:
```bash
kafka-topics.sh --bootstrap-server localhost:9092 \
--describe --topic orders
# Look for:
# Leader: 2 (new leader)
# Replicas: 1,2,3
# Isr: 2,3 (Broker 1 not yet in ISR)
# After catch-up:
# Isr: 1,2,3 (Broker 1 rejoined)
Check under-replicated partitions:
kafka-topics.sh --bootstrap-server localhost:9092 \
--describe --under-replicated-partitions
# Should show partitions where ISR < Replicas
# Decreases to 0 as Broker 1 catches up
JMX metrics:
# Under-replicated partitions
kafka.server:type=ReplicaManager,name=UnderReplicatedPartitions
└─ Should decrease to 0
# Replica lag (on Broker 1)
kafka.server:type=FetcherLagMetrics,name=ConsumerLag,clientId=ReplicaFetcherThread-0-2
└─ Lag decreases as replication progresses
# Fetch rate (on Broker 1)
kafka.server:type=BrokerTopicMetrics,name=TotalFetchRequestsPerSec
└─ High rate indicates active replication
Controller logs:
INFO [Controller] Partition [orders,0] ISR updated to [2,3]
INFO [Controller] Partition [orders,0] ISR expanded to [1,2,3]
---
### Best Practices for Broker Recovery
OPERATIONAL RECOMMENDATIONS:
- Monitor under-replicated partitions:
# Continuous monitoring
watch -n 5 'kafka-topics.sh --bootstrap-server localhost:9092 \
--describe --under-replicated-partitions | wc -l'
- Increase replication speed (if needed):
# Broker config
num.replica.fetchers=4 # More threads for replication (default: 1)
replica.fetch.max.bytes=1048576 # Larger fetch batches (default: 1 MB)
replica.fetch.min.bytes=1 # Minimum bytes before response
- Prevent data loss:
# Topic/broker config
min.insync.replicas=2 # Require 2 ISR members for writes
unclean.leader.election.enable=false # Never elect out-of-sync leader
acks=all # Producer waits for all ISR (producer config)
Plan for broker restarts: ├─ Use controlled shutdown (graceful) ├─ Monitor ISR after restart ├─ Verify partitions caught up before next maintenance └─ Avoid multiple broker restarts simultaneously
Alert on prolonged under-replication:
# Alert if under-replicated for >10 minutes
if [ $(kafka-topics.sh --describe --under-replicated-partitions | wc -l) -gt 0 ]; then
echo "ALERT: Under-replicated partitions detected"
fi
---
### Recovery Time Estimation
FACTORS AFFECTING RECOVERY TIME:
Data size to replicate: └─ Lag: Leader LEO - Follower LEO
Network bandwidth: └─ Example: 1 Gbps network = ~120 MB/s
Disk I/O speed: └─ SSDs: Faster replication than HDDs
Replication configuration: └─ num.replica.fetchers: More threads = faster └─ replica.fetch.max.bytes: Larger batches = faster
EXAMPLE CALCULATION:
Lag: 10 GB Network: 1 Gbps (120 MB/s) Disk: SSD (500 MB/s write) Bottleneck: Network (120 MB/s)
Recovery time: └─ 10 GB / 120 MB/s = ~85 seconds
Plus overhead (metadata, index rebuild): └─ Total: ~2 minutes
REALISTIC SCENARIOS:
Small lag (<1 GB): └─ Recovery: <30 seconds
Medium lag (1-10 GB): └─ Recovery: 1-5 minutes
Large lag (>10 GB): └─ Recovery: 10+ minutes └─ Consider: Is data needed? Maybe remove and add as new replica
---
## 9. Key Takeaways
**You've learned:**
✅ **Single broker failure** — ISR-based failover, brief unavailability
✅ **Multiple broker failures** — may lead to offline partitions or unclean election
✅ **Network partitions** — controller-side continues, isolated side unavailable
✅ **Split-brain prevented** — centralized controller coordination
✅ **Uncommitted data loss** — expected with acks < all
✅ **Committed data loss** — only with unclean election
✅ **Cascading failures** — ISR shrinks under load, capacity planning critical
✅ **Recovery patterns** — truncation, replication, ISR expansion
✅ **Correlated failures** — shared infrastructure risks
✅ **Controller + leader failure** — both recover independently
---
## Summary
**Kafka failover is robust but has trade-offs:**
Failure Scenarios = ├─ Single broker: Clean failover (ISR-based) ├─ Multiple brokers: May go offline (if ISR exhausted) ├─ Network partition: Controller-side continues └─ All replicas down: Complete unavailability
Data Loss Boundaries = ├─ acks=0/1: Data loss expected (no replication guarantee) ├─ acks=all: No loss (if unclean election disabled) └─ Unclean election: Committed data may be lost
Recovery = ├─ Broker restart: Truncate divergent, replicate, rejoin ISR ├─ Disk failure: Full replication from leader ├─ Cascading failures: Capacity planning critical └─ Split-brain: Prevented by centralized controller
Configuration = ├─ unclean.leader.election.enable=false (prevent committed data loss) ├─ min.insync.replicas=2 (ensure durability) └─ RF=3 (tolerate up to 2 failures)
**Remember:** Kafka's failover behavior is well-defined. Understanding failure scenarios helps you configure appropriate durability settings and plan for disaster recovery.
**Next up:** Module 5.1 — Metadata Model & Cluster State — How Kafka tracks cluster topology.