Skip to content

AxonOps — AI-Native Control Plane for Open Source Data Platforms

Kafka Replication

Kafka's replication protocol provides fault tolerance and high availability by maintaining multiple copies of each partition across different brokers. This document covers both the mechanics of replication and the design rationale behind each mechanism.


Replication addresses three problems inherent to distributed systems:

ProblemSolution
Data lossMultiple copies survive individual failures
AvailabilityAnother replica serves requests when one fails
DurabilityAcknowledged writes exist on multiple machines

However, replication introduces consistency challenges, performance overhead, and complexity in failure handling. Kafka's replication protocol—built around ISR, leader election, and high watermark—addresses these challenges through specific design choices.


Each partition has multiple replicas distributed across brokers. One replica is the leader; others are followers.

Leader and follower replicas of a partition with replication factor 3Leader and follower replicas of a partition with replication factor 3Partition 0 (RF=3)Broker 1LeaderBroker 2FollowerBroker 3FollowerProducerWrites go to leader onlyFollowers pull from leaderproducereplicatereplicate

Consumers fetch from partition leaders by default. With multiple partitions distributed across brokers, a consumer fetches from multiple leaders simultaneously:

Consumer fetching each partition from its leader across three brokersConsumer fetching each partition from its leader across three brokersTopic: orders (3 partitions, RF=3)Broker 1Broker 2Broker 3P0 LeaderP1 FollowerP2 FollowerP0 FollowerP1 LeaderP2 FollowerP0 FollowerP1 FollowerP2 LeaderConsumer(subscribed to orders)Each partition: 1 leader (green) + 2 followers (yellow)Consumer fetches from each partition's leaderLeaders distributed across brokers for load balancingfetch P0fetch P1fetch P2
Client TypeTarget ReplicaNotes
ProducerLeader onlyRequired for ordering and consistency
ConsumerLeader (default)Ensures reading up to high watermark
ConsumerFollower (optional)KIP-392, Kafka 2.4+; reduces cross-datacenter traffic
FollowerLeaderReplication protocol

Since Kafka 2.4, consumers can fetch from follower replicas instead of the leader. This feature reduces cross-datacenter network traffic by allowing consumers to read from replicas in the same rack or availability zone.

Configuration:

# Broker: assign rack ID
broker.rack=us-east-1a
# Consumer: enable rack-aware fetching
client.rack=us-east-1a

When client.rack matches a follower's broker.rack, the consumer prefers fetching from that follower instead of the leader.

Trade-offs:

AspectLeader FetchingFollower Fetching
Data freshnessUp to high watermarkSlightly behind leader
Network costCross-rack/AZ possibleSame rack/AZ preferred
ConsistencyReads HW directlyFollower's HW may lag slightly

Follower HW Lag

Followers learn the high watermark from fetch responses. A follower's HW may lag slightly behind the leader's HW, meaning consumers fetching from followers may see data become visible slightly later than those fetching from the leader.

Kafka uses single-leader replication rather than multi-leader or leaderless approaches:

ApproachTrade-offKafka
Single-leaderSimpler consistency, potential bottleneck✅ Used
Multi-leaderHigher write availability, conflict resolution needed❌ Not used
LeaderlessNo single point of failure, complex consistency❌ Not used

Single-leader replication provides strong ordering guarantees within a partition. All writes pass through one broker, establishing a definitive order. Followers replicate this order, avoiding conflict resolution complexity.


The ISR is the set of replicas that are "sufficiently caught up" with the leader. ISR is central to Kafka's consistency and availability guarantees.

Two alternative approaches to replication illustrate the ISR design:

Synchronous replication requires waiting for all replicas before acknowledging writes:

  • Benefit: If the leader fails, all replicas have all acknowledged data—no data loss
  • Drawback: One slow replica blocks the entire partition
  • Drawback: One failed replica makes the partition unavailable for writes

Asynchronous replication acknowledges immediately after leader persistence:

  • Benefit: Fast acknowledgment; no blocking on slow or failed replicas
  • Drawback: If the leader fails, data not yet replicated is lost
  • Drawback: No durability guarantee for acknowledged writes

ISR provides a middle ground: a dynamic set of replicas that are "caught up enough" to be trusted. With acks=all, writes wait for all ISR members, not all replicas. If a replica falls behind or fails, it is removed from ISR rather than blocking writes—the definition of "all" shrinks to exclude problematic replicas.

This means ISR provides durability guarantees only across the current ISR membership. If ISR shrinks to just the leader, acks=all behaves like acks=1—a subsequent leader failure loses data. The min.insync.replicas setting prevents this by refusing writes when ISR is too small.

Replication trade-offs between synchronous, ISR-based, and asynchronous acknowledgementReplication trade-offs between synchronous, ISR-based, and asynchronous acknowledgementReplication Trade-offsSynchronous(wait for all)ISR(wait for caught-up)Asynchronous(wait for none)High durabilityLow availabilityHigh latencyTunable durabilityHigh availabilityBalanced latencyLow durabilityHigh availabilityLow latencyRemove slowreplicas fromwaiting set

A replica remains in the ISR when meeting both conditions:

CriterionConfigurationDefault
Caught upWithin replica.lag.time.max.ms of leader30000 (30s)
ConnectedActive session with leader-

Time-Based vs Count-Based Lag

Earlier Kafka versions used message count (replica.lag.max.messages) to determine ISR membership. This approach caused problems: during write bursts, replicas would temporarily fall out of ISR even when healthy. The time-based approach (replica.lag.time.max.ms) provides more stability—a replica that actively fetches remains in ISR regardless of temporary lag.

ISR shrink and expand as a follower falls behind and catches upLeaderLeaderLeaderFollower AFollower AFollower AFollower BFollower BFollower BLeaderLeaderFollower AFollower AFollower BFollower BISR = {Leader, A, B}receive produce (offset 100)replicatereplicatefetch (caught up to 100)A remains in ISRslow disk I/OFalls behind30 seconds passcheck replica lagB exceeded replica.lag.time.max.msshrink ISRISR = {Leader, A}B removed but not failedB catches upfetch (caught up)expand ISRISR = {Leader, A, B}

ISR serves three purposes:

PurposeMechanism
Write acknowledgmentWith acks=all, producers wait for all ISR members
Leader electionOnly ISR members can become leader (by default)
Durability guaranteeData on all ISR members survives leader failure
ConfigurationDefaultDescription
replica.lag.time.max.ms30000Max time follower can lag before ISR removal
min.insync.replicas1Minimum ISR size for acks=all produces
unclean.leader.election.enablefalseAllow non-ISR replicas to become leader

The min.insync.replicas setting establishes a durability floor—the minimum number of replicas that must have data before it is considered safe:

RFmin.insync.replicasBehaviorUse Case
31Write succeeds if leader alone persistsPerformance over durability
32Write requires leader + 1 followerRecommended: survives 1 failure
33Write requires all replicasMaximum durability, reduced availability

ISR Shrinkage Impact

If ISR size falls below min.insync.replicas, producers with acks=all receive NotEnoughReplicasException. The partition becomes unavailable for writes until ISR recovers. This behavior is intentional: Kafka refuses writes that cannot meet the configured durability guarantee.


When a partition leader fails, Kafka elects a new leader to restore availability. The election process determines which replica becomes the new leader and the fate of uncommitted data. For controller-side election coordination, see Cluster Management.

CapabilityMechanism
Automatic failoverNew leader takes over without manual intervention
Bounded unavailabilityPartition offline only during election; duration depends on timeouts and load
Data preservationISR-based election prevents data loss

When the leader fails, the controller elects a new leader from the ISR:

Clean leader election from the ISR after a leader broker failsControllerBroker 1Broker 2Broker 3ControllerControllerBroker 1(Old Leader)Broker 1(Old Leader)Broker 2(ISR)Broker 2(ISR)Broker 3(ISR)Broker 3(ISR)Broker 1 failsdetect failure(session timeout)Election criteria:1. Must be in ISRselect B2 as new leaderLeaderAndIsr(leader=B2, epoch=2)LeaderAndIsr(leader=B2, epoch=2)become leaderfollow B2New leader: Broker 2ISR: {B2, B3}Leader epoch: 2

Electing only from ISR ensures the new leader has all committed data:

Scenario: Leader has offsets 0-99, committed (HW=100)
Leader fails
ISR replica (offset 99): Has all committed data ✓
Non-ISR replica (offset 50): Missing offsets 51-99 ✗

If a non-ISR replica became leader, offsets 51-99 would be lost despite having been acknowledged to producers.

When all ISR replicas fail simultaneously, Kafka must choose between availability and consistency:

ChoiceConfigurationConsequence
Wait for ISRunclean.leader.election.enable=falsePartition unavailable until ISR returns
Elect non-ISRunclean.leader.election.enable=trueData loss possible, but partition available

Data Loss Risk

Unclean leader election can lose acknowledged data. Example: A producer receives acknowledgment for offset 100. All ISR members fail. A non-ISR replica with offset 80 becomes leader. Offsets 81-100 are permanently lost despite successful acknowledgment.

Enable unclean election when:

  • Availability takes priority over consistency
  • Data can be reconstructed from source systems
  • Loss is acceptable (e.g., metrics, logs)

Keep disabled (default) when:

  • Processing financial transactions
  • Maintaining audit logs
  • Handling any data where loss is unacceptable

Followers do not receive pushed data; they pull from the leader. This pull-based model enables flow control and simplifies leader responsibilities.

ApproachTrade-off
Push (leader → follower)Leader must track each follower's state; complex backpressure
Pull (follower → leader)Follower controls pace; leader treats all fetches uniformly

Kafka uses pull-based replication. The same fetch mechanism serves both followers and consumers, simplifying the codebase.

Pull-based replication with a follower fetcher threadPull-based replication with a follower fetcher threadLeader (Broker 1)Follower (Broker 2)Partition Log(offsets 0-100)Replica Log(offsets 0-95)Replica FetcherThreadContinuous polling loop:1. Fetch from leader2. Append to local log3. RepeatFetchRequest(offset=96, maxBytes=1MB)FetchResponse(records 96-100)append records
ConfigurationDefaultPurpose
num.replica.fetchers1Parallel fetch threads per source broker
replica.fetch.min.bytes1Minimum bytes before responding
replica.fetch.max.bytes1048576Maximum bytes per fetch
replica.fetch.wait.max.ms500Max wait for min.bytes
replica.fetch.backoff.ms1000Backoff after fetch error

The high watermark (HW) is the offset up to which all ISR replicas have replicated. It represents the boundary between "safe" and "potentially lost" data.

Without high watermark, the following scenario is possible:

1. Producer writes offset 100 to leader
2. Consumer immediately reads offset 100
3. Leader fails before replicating to followers
4. New leader elected with offset 99
5. Consumer has data that no longer exists in Kafka

High watermark prevents this by exposing only data replicated to all ISR:

1. Producer writes offset 100 to leader (HW still 99)
2. Consumer can only read up to offset 99 (HW)
3. Followers replicate offset 100
4. Leader advances HW to 100
5. Consumer can now read offset 100
High watermark advancing once all ISR replicas reach the leader log end offsetProducerLeaderFollower AFollower BProducerProducerLeader(LEO=100)Leader(LEO=100)Follower A(LEO=98)Follower A(LEO=98)Follower B(LEO=99)Follower B(LEO=99)HW = min(LEO of all ISR) = 98produce recordappend (LEO=101)HW still 98Record not yet visiblefetch(offset=98)records 98-100, HW=98append (LEO=101)fetch(offset=99)records 99-100, HW=98append (LEO=101)All ISR now at LEO=101advance HW to 101Record now visible to consumers
RoleVisible DataRationale
ConsumerUp to HWOnly "safe" data that survives failures
FollowerAll fetched dataMust replicate everything
LeaderAll written dataOwns the authoritative log

Read-After-Write Latency

Producers do not immediately see their own writes when consuming. Records become visible only after HW advances—requiring all ISR members to replicate. This latency is typically milliseconds but can increase under load.


The acks setting controls the durability-latency trade-off for producers, determining how many replicas must have data before Kafka acknowledges success.

Different applications have different durability requirements:

ApplicationPriorityAppropriate acks
Metrics/telemetryThroughput over durability0 or 1
Application logsBalance1
Financial transactionsDurability over throughputall
acksBehaviorDurabilityLatency
0No acknowledgment waitedNone—fire and forgetLowest
1Wait for leader persistenceLeader only—lost if leader fails before replicationLow
allWait for all ISR persistenceDepends on min.insync.replicasHigher

These two settings work together:

  • acks=all: Producer waits for all ISR
  • min.insync.replicas: Minimum ISR size to accept writes
Write acknowledgement with acks=all and min.insync.replicas=2Write acknowledgement with acks=all and min.insync.replicas=2acks=all, min.insync.replicas=2, RF=3LeaderFollower 1 (ISR)Follower 2 (ISR)ProducerIf one follower fails, writes continue (2 ISR remain)If two followers fail, writes blocked (only 1 ISR)1. produce4. ack (≥2 ISR confirmed)2. replicate3. ack2. replicate3. ack

Common configurations:

RFmin.insync.replicasacksSurvivesUse Case
32all1 failureProduction default
31all0 failures after ackDevelopment/test
321Leader-only durabilityHigh throughput

Leader epochs solve a critical problem: divergent logs after leader changes.

Without epochs, the following scenario creates inconsistent data:

Time 1: Leader A writes offset 100 (not yet replicated)
Time 2: Network partition—A cannot reach followers or controller
Time 3: Controller elects B as new leader
Time 4: B writes offset 100 (different data)
Time 5: Partition heals—both A and B have offset 100 with different data

Without epochs, determining which offset 100 is correct is impossible.

Each leader term receives a monotonically increasing epoch number. Operations carry their epoch, and stale epochs are rejected:

Epoch 0: Leader A (offsets 0-99)
A writes offset 100 with epoch 0
Epoch 1: Leader B (elected during partition)
B writes offset 100 with epoch 1
When A reconnects:
- A's epoch 0 < current epoch 1
- A becomes follower
- A truncates to last offset from epoch 0 (offset 99)
- A fetches offset 100 from B (the authoritative version)
ScenarioResolution
Network partitionOld leader's writes rejected (stale epoch)
Split brainOnly current epoch accepted; conflicts resolved by truncation
Log recoveryFollowers truncate to epoch boundary, then catch up

Each partition maintains an epoch checkpoint:

Terminal window
cat /var/kafka-logs/orders-0/leader-epoch-checkpoint
# epoch start_offset
0 0
1 100
2 250

This file records when each epoch started, enabling recovery after failures.


Over time, leader distribution becomes unbalanced. Preferred replica election restores balance.

Initial: Broker 1: Leader for P0, P1
Broker 2: Leader for P2, P3
After B1 fails and recovers:
Broker 1: Follower for everything
Broker 2: Leader for P0, P1, P2, P3 ← Overloaded

The "preferred replica" is the first replica in the assignment list. Preferred election moves leadership back to this replica.

ConfigurationDefaultPurpose
auto.leader.rebalance.enabletrueAutomatically elect preferred leaders
leader.imbalance.check.interval.seconds300Check frequency
leader.imbalance.per.broker.percentage10Trigger threshold
Terminal window
# Trigger preferred replica election for all partitions
kafka-leader-election.sh --bootstrap-server kafka:9092 \
--election-type preferred \
--all-topic-partitions
# Trigger for specific topic
kafka-leader-election.sh --bootstrap-server kafka:9092 \
--election-type preferred \
--topic orders

Replication health is critical. Under-replicated partitions indicate problems that may become failures. For complete failure handling and recovery procedures, see Fault Tolerance.

MetricDescriptionAlert Threshold
UnderReplicatedPartitionsPartitions with ISR < RF> 0
UnderMinIsrPartitionCountPartitions below min.insync.replicas> 0 (critical)
OfflinePartitionsCountPartitions without leader> 0 (critical)
ReplicaLagTimeMaxMax follower lag in msSustained high values
IsrShrinksPerSecRate of ISR shrinkageElevated = instability
IsrExpandsPerSecRate of ISR expansionShould follow shrinks
ConditionIndicationAction
Under-replicated > 0Followers behind or offlineCheck follower broker health, disk I/O
Under-min-ISR > 0Durability at riskUrgent: restore replicas or reduce load
Offline > 0No leader availableCritical: check broker status, consider unclean election
High lag timeSlow replicationCheck network, disk, CPU on followers
Frequent ISR changesUnstable clusterCheck for overload, network issues
Terminal window
# Check under-replicated partitions
kafka-topics.sh --bootstrap-server kafka:9092 \
--describe --under-replicated-partitions
# Check offline partitions
kafka-topics.sh --bootstrap-server kafka:9092 \
--describe --unavailable-partitions
# Describe specific topic's replicas
kafka-topics.sh --bootstrap-server kafka:9092 \
--describe --topic orders
# Output shows:
# Topic: orders Partition: 0 Leader: 1 Replicas: 1,2,3 Isr: 1,2
# ↑ Broker 3 not in ISR