Two offsets on every replica
Every replica tracks 2 positions. The log end offset is the offset of the next record it will append, so a replica whose last record sits at offset 7 has a log end offset of 8. The high watermark is the offset up to which every replica in the in-sync set holds the data. On the leader, the high watermark is the smallest log end offset among the in-sync replicas. A record below the high watermark is committed: it survives the loss of any single in-sync replica, because every one of them has it.
| Moment | Leader log end offset | Follower A next fetch | Follower B next fetch | High watermark | A reader can see |
|---|---|---|---|---|---|
| Record 7 appended on the leader | 8 | 7 | 7 | 7 | offsets 0 to 6 |
| A's fetch for offset 7 returns record 7 | 8 | 7 (writing it) | 7 | 7 | offsets 0 to 6 |
| A asks for offset 8, B asks for offset 7 | 8 | 8 | 7 | 7 | offsets 0 to 6 |
| B asks for offset 8 | 8 | 8 | 8 | 8 | offsets 0 to 7 |
What the reader is allowed to see
- ▸Suppose the leader handed out offset 7 the moment it was appended, and its disk died 10 ms later.
- ▸Neither follower had fetched offset 7 yet, so the new leader's log ends at offset 7 and the record is gone.
- ▸A reader that already processed record 7 has acted on data the cluster no longer holds, and a reader starting later will never see it.
- ▸Waiting for the high watermark means every record a reader sees is one every in-sync replica holds, so no leader change can take it back.
Reading the mark from the command line
kafka-get-offsets.sh --bootstrap-server broker1:9092 --topic orders prints 1 line per partition in the form orders:0:8, and the number is the high watermark, the first offset no reader has been given. When it stops moving while the producer keeps sending, a follower is holding it, and the next 2 sections show what the leader does about it.
Replication factor 3 promises 3 copies of every record, but the promise only holds while 3 brokers are up. With min.insync.replicas at its default of 1, a partition whose 2 followers have both dropped out of the in-sync set keeps accepting writes with the leader as the only replica, and the leader confirms each record as fully replicated because, among the in-sync replicas, it is. If that broker's disk fails before the followers return, those records are gone. min.insync.replicas sets the floor: the number of in-sync replicas a partition must have before it accepts a write that asked for full replication.
Which writes it refuses
A producer chooses how much confirmation it wants through its acks setting. With acks=all, the leader confirms a record only after every in-sync replica has it, which in high watermark terms means after the mark has passed the record. That is the write min.insync.replicas guards. When the in-sync set is smaller than the minimum, the leader answers an acks=all send with the error NotEnoughReplicas and appends nothing. A producer sending with acks=1 asked only for the leader's confirmation, and the leader keeps accepting those writes with 1 copy, minimum or not. The setting protects producers that asked for safety and does nothing for ones that did not.
With the eligible leader replicas feature, on by default on clusters newly formatted with a recent 4.x release and switched on by an operator after an upgrade, the rule also reaches the high watermark: while the in-sync set is below the minimum, the leader stops advancing the mark. Records that arrived with acks=1 during the shortage stay unreadable until enough replicas are back in sync, so no reader acts on a record only 1 disk holds.
Setting it
The setting lives on the topic, with a broker-wide default for topics that do not set their own. The usual pairing is replication factor 3 with a minimum of 2. kafka-configs.sh --bootstrap-server broker1:9092 --alter --entity-type topics --entity-name payments --add-config min.insync.replicas=2 raises it on 1 topic at once. The broker properties below set the floor for every topic without its own value, existing topics included, once each broker restarts with them; default.replication.factor shapes only topics created afterwards.
| Replication factor | min.insync.replicas | Brokers that can be lost while acks=all writes continue | Copies every confirmed record has |
|---|---|---|---|
| 3 | 1 | 2 | at least 1 |
| 3 | 2 | 1 | at least 2 |
| 3 | 3 | 0 | 3 |
| 2 | 2 | 0 | 2 |
| 5 | 3 | 2 | at least 3 |
The trade behind the number
With 1 broker down the in-sync set holds 2 replicas, the minimum, and both variants keep writing. The second kill drops the set to 1. In the acks=all variant every send from that point comes back with NotEnoughReplicas and the leader's log stops growing; in the acks=1 variant the log keeps growing on a single disk, which is the situation the setting exists to prevent.
- Pair replication factor 3 with min.insync.replicas=2 for most topics
- Set the broker default so every topic without its own value inherits it
- Alert on UnderMinIsrPartitionCount, since producers are being refused
- Raise the minimum to the replication factor only where pausing beats losing a record
- Leave min.insync.replicas at 1 on a topic that cannot lose records
- Set the minimum above the replication factor
- Expect it to protect producers that send with acks=1
- Treat NotEnoughReplicas as a producer bug; it is a broker count
In sync is a statement about time. A follower is in sync when it has fetched up to the leader's log end offset at least once in the last replica.lag.time.max.ms milliseconds, 30,000 by default. The leader records the moment each follower last caught up fully, and a follower whose moment is older than 30 seconds is removed from the in-sync set. The number of records behind does not enter the rule: a follower 5,000 records behind a busy leader that caught up 2 seconds ago is in sync, and a follower 1 record behind that has not reached the end for 31 seconds is out.
Timeline of a shrink
| Clock | What happens | In-sync set | High watermark |
|---|---|---|---|
| 0 s | Broker 3 enters a long garbage collection pause and stops fetching | 1, 2, 3 | stuck at 1,200 |
| 0 to 30 s | Leader appends 3,000 records; broker 2 copies them all; broker 3 copies none | 1, 2, 3 | stuck at 1,200 |
| 30 s | Leader's check finds broker 3 last caught up 30 s ago and removes it | 1, 2 | jumps to 4,200 |
| 30 s | Leader sends the new set to the controller, which records it in the metadata log | 1, 2 | 4,200 |
| 45 s | Broker 3 wakes and resumes fetching from offset 1,200 | 1, 2 | advancing |
| 52 s | Broker 3 reaches the high watermark and is added back | 1, 2, 3 | advancing |
For those 30 seconds every record the leader accepted sat above the high watermark, confirmed to no acks=all producer and readable by nobody. Removing the follower unblocks the partition: once the set is brokers 1 and 2, broker 2's log end offset sets the mark, which jumps 3,000 offsets and hands stalled readers a burst. With a minimum of 2, the partition still accepts fully replicated writes, 1 broker away from refusing them.
The leader runs that check every replica.lag.time.max.ms divided by 2, so a follower leaves between 30 and 45 seconds after it stalls. The leader proposes the smaller set to the controller and keeps using the old one until the controller has recorded the change, so a slow controller keeps the mark pinned a little longer.
What makes a follower slow
- A disk that cannot keep upwrites stallThe follower appends slower than the leader, falls behind during bursts and never quite reaches the end.
- A saturated or partitioned network linkfetches time outCross-zone links are the common case: replication traffic competes with everything else on them.
- A long garbage collection pausethe broker freezesNothing is fetched for seconds; a pause past 30 s costs the broker its place in every partition it follows.
- Too many partitions on 1 brokerfetcher threads overloadedThe follower rotates through thousands of partitions and each one waits its turn.
- A restartthe broker is gone, then backA broker that stops is removed as soon as the controller notices it is gone, then catches up on everything it missed.
Coming back
kafka-topics.sh --bootstrap-server broker1:9092 --describe --under-replicated-partitions lists every partition whose in-sync set is smaller than its replica list, and prints nothing on a healthy cluster. The broker metric UnderReplicatedPartitions counts the same per broker, and UnderMinIsrPartitionCount counts the partitions below their minimum, which are refusing acks=all writes. A few under-replicated partitions for a minute during a rolling restart is normal; a list that stays put points at a follower that cannot keep up.
Broker 1 leads partition 0 of payments and stops answering. The active controller, the node that keeps the cluster's metadata, expects a heartbeat from every broker, and once broker.session.timeout.ms, 9,000 ms by default, passes with no heartbeat from broker 1, it marks broker 1 fenced, strips it out of every in-sync set, and picks a new leader for each partition broker 1 led. The election itself takes milliseconds and consults no producer or consumer; clients find the new leader when a request to the old one fails and they refresh their metadata.
A fenced broker leads nothing and holds no place in any in-sync set. A silent broker is fenced after the 9 second heartbeat timeout, well before the 30 second lag rule would have removed it as a slow follower.
The rule
The fields below are the ones kafka-topics.sh --describe prints for a partition. Broker 2 wins because it comes before broker 0 in the list and both are in sync.
| Moment | Leader | Replicas | Isr | Elr |
|---|---|---|---|---|
| Before | 1 | 1, 2, 0 | 1, 2, 0 | |
| Broker 1 fenced | 2 | 1, 2, 0 | 2, 0 | |
| Broker 1 back, catching up | 2 | 1, 2, 0 | 2, 0 | |
| Broker 1 reaches the mark | 2 | 1, 2, 0 | 2, 0, 1 | |
| Preferred leader election | 1 | 1, 2, 0 | 2, 0, 1 |
What happens to the old leader's extra records
Broker 1 may have held records above the high watermark that no follower had fetched: appended, confirmed to an acks=1 producer, read by nobody. Each election raises the partition's leader epoch, a counter every replica stores alongside its log and every request carries, so when broker 1 returns it is a follower with a stale epoch. It asks the new leader where the new epoch began, truncates its own log back to that offset, which discards the unreplicated records, and fetches forward from there. The acks=1 producer lost records it was told were stored, which is the risk it accepted, and no reader lost anything, because readers were held at the mark.
When nobody in sync is left
The in-sync set can be empty: brokers 2 and 0 die together, or 1 follower was already out of the set when the leader died. On its own, the in-sync rule then leaves the controller no safe candidate. The eligible leader replicas feature, on by default on clusters newly formatted with a recent 4.x release and switched on by an operator after an upgrade, adds a second list, which the describe output prints as Elr. A replica joins it when its own exit from the in-sync set takes the set below min.insync.replicas. The mark cannot advance while the set is short, so that replica still holds every committed record and the controller may elect it. A replica that has lost its data, such as one restarting onto a fresh disk, is taken off the list.
If the in-sync set and the eligible list are both empty, the partition goes offline: the describe output shows Leader: none, every read and write to it fails, and the partition waits for a replica from one of the 2 lists to come back. The controller would rather serve nothing than elect a replica that is missing committed records. An operator can override that and accept the loss; that choice has a lesson of its own.
Giving the leader back
Broker 1 rejoins as a follower while broker 2 keeps leading, so after a few restarts leadership piles up on whichever brokers stayed up. With auto.leader.rebalance.enable at its default of true, the controller checks every leader.imbalance.check.interval.seconds, 300 by default, and moves leadership back to the preferred replica on any broker whose share of non-preferred leaders exceeds 10 percent. kafka-leader-election.sh --bootstrap-server broker1:9092 --election-type preferred --all-topic-partitions does the same on demand, which is how an operator restores balance right after a rolling restart instead of waiting 5 minutes.
Telling the broker where it is
broker.rack is a label and nothing more; the broker does not check it against anything. The controller reads it when a topic is created or gains partitions and places each partition's replicas so that they spread across as many distinct racks as the replication factor allows, which with 3 racks and replication factor 3 means 1 replica per rack for every partition. It also rotates which broker holds the first replica, so leadership spreads evenly. The placement happens once, at creation: adding a rack label to a running broker changes nothing about partitions that already exist.
| Partition | Replicas (brokers) | Racks | Leader |
|---|---|---|---|
| 0 | 0, 1, 2 | A, B, C | 0 |
| 1 | 1, 2, 3 | B, C, A | 1 |
| 2 | 2, 3, 4 | C, A, B | 2 |
| 3 | 3, 4, 5 | A, B, C | 3 |
| 4 | 4, 5, 0 | B, C, A | 4 |
| 5 | 5, 0, 1 | C, A, B | 5 |
Every partition has 1 copy in each zone, and each broker leads exactly 1 partition and follows 2. With min.insync.replicas set to 2, the table also tells you what the cluster can survive.
Partition 0: broker 0 leads, and each of its 2 followers sits in a different zone, so losing any 1 zone leaves 2 copies.
Zone B loses power
| Partition | Leader | In-sync set | Fully acknowledged writes with a minimum of 2 |
|---|---|---|---|
| 0 | 0 | 0, 2 | accepted |
| 1 | 2 | 2, 3 | accepted |
| 2 | 2 | 2, 3 | accepted |
| 3 | 3 | 3, 5 | accepted |
| 4 | 5 | 5, 0 | accepted |
| 5 | 5 | 5, 0 | accepted |
Three numbers that have to agree
- ▸It places replicas when a topic is created or gains partitions; it does not move existing partitions when a rack label is added later.
- ▸It balances racks, not disks: 2 brokers in the same rack can hold very different amounts of data.
- ▸It cannot invent copies: replication factor 2 across 3 racks still leaves every partition 1 rack failure away from a single copy.
- ▸Reads still go to the leader, so a consumer in zone C reading a partition led from zone A pays cross-zone traffic on every fetch; a later lesson covers reading from the nearest replica.
> A ride-hailing platform stores trip state changes, driver locations and fare calculations in topics on a 9 broker cluster spread across 3 cloud zones, 3 brokers per zone, with replication factor 3, rack labels set, and min.insync.replicas at 2 on the trip and fare topics and 1 on driver locations. At the start of the Friday evening rush, one zone loses network connectivity for 4 minutes.
Apache Kafka is an open-source distributed event streaming platform used by thousands of companies for high-performance data pipelines, streaming analytics, data integration, and mission-critical applications.
kafka.apache.org
- The High Watermark
- Replicated Logs: Quorums, ISRs, and State Machines
- Minimum In-Sync Replicas
- min.insync.replicasAvailability and Durability Guarantees
- Followers That Fall Behind
- replica.lag.time.max.msReplica Management
- Electing a New Leader
- Unclean leader electionBalancing leadership
- Replicas Across Racks
- Balancing replicas across racksbroker.rack
Apache Kafka, Kafka, and the Kafka logo are either registered trademarks or trademarks of The Apache Software Foundation in the United States and other countries.
Follow a record from the leader's disk to a reader's screen, and learn what the cluster does when a copy, a broker or a whole zone disappears.
- Category
- Kafka
- Difficulty
- intermediate
- Duration
- 20 minutes
- Challenges
- 7 hands-on challenges
Topics covered: The High Watermark, Minimum In-Sync Replicas, Followers That Fall Behind, Electing a New Leader, Replicas Across Racks
Lesson Sections
- The High Watermark (concepts: In-sync replicas)
A producer sends a record to the partition's leader on broker 1. The leader appends it at offset 7 and the record now exists on exactly 1 disk in the cluster. A consumer fetching from offset 7 at that instant gets back an empty response. The leader holds the record back until every in-sync follower has copied it, and the offset that marks how far that copying has reached is the high watermark. Two offsets on every replica Take a partition with replication factor 3: the leader on broker 1, follow
- Minimum In-Sync Replicas (concepts: In-sync replicas)
Which writes it refuses The error is retriable: the producer's client resends on its own, so a shortage of a few seconds looks to the application like a slow send. One that outlasts the client's patience surfaces as an exception, which tells the application its data is not being stored to the standard it asked for. Setting it The trade behind the number Raising the minimum trades availability for durability. At replication factor 3 and a minimum of 2, 1 broker can be down and every confirmed rec
- Followers That Fall Behind (concepts: In-sync replicas)
Time works where a count would not: a partition taking 10,000 records a second always has a follower hundreds of records behind mid-fetch, so a count threshold would flap on every burst. Timeline of a shrink What makes a follower slow Coming back A follower rejoins once the offset it requests reaches the current high watermark, which can be short of the leader's log end. The leader then proposes adding it back, and the set grows when the controller records the change. From then on the mark advan
- Electing a New Leader (concepts: Replication and leaders)
A fenced broker leads nothing and holds no place in any in-sync set. A silent broker is fenced after the 9 second heartbeat timeout, well before the 30 second lag rule would have removed it as a slow follower. The rule The controller walks the partition's replica list in order and picks the first replica that is both alive and in the in-sync set. The replica list is the one assigned to the partition, which only a reassignment changes, and its first entry is the preferred leader, so when nothing
- Replicas Across Racks (concepts: Replication and leaders)
Six brokers run across 3 availability zones, 2 brokers in each: brokers 0 and 3 in zone A, 1 and 4 in zone B, 2 and 5 in zone C. A topic with 6 partitions and replication factor 3 is created on a cluster that knows nothing about the zones, so the controller spreads replicas across brokers and nothing else. A partition placed on brokers 0, 3 and 1 has 2 of its 3 copies in zone A, and when zone A loses power it keeps 1 in-sync replica, below a minimum of 2, and refuses writes until the zone return