Learning Platform
Глоссарий Troubleshooting
Урок 02.03 · 25 мин
Продвинутый
ReplicationISRHigh WatermarkLEOacks

Репликация и ISR

Kafka может пережить потерю брокера без потери данных. Это не магия — это точно определённый протокол репликации на основе ISR (In-Sync Replicas). Понимание этого протокола критически важно: неправильная конфигурация acks и min.insync.replicas — одна из самых частых причин потери данных в production.


Модель лидер/фолловер: все записи через лидера

Каждая партиция имеет ровно одного лидера. Все продюсерские запросы (ProduceRequest) и потребительские запросы (FetchRequest) обрабатываются лидером.

Фолловеры не взаимодействуют с клиентами. Они постоянно посылают лидеру FetchRequest с указанием своего текущего LEO (Log End Offset):

Follower → Leader: FetchRequest { offset: 100, max_bytes: 1MB }
Leader  → Follower: FetchResponse { records: [offset 100..150] }

Это pull-модель репликации: фолловер сам запрашивает данные, лидер не толкает их. Преимущество: фолловер контролирует темп репликации, нет риска перегрузки медленной реплики.

Репликация партиции: ISR и High Watermark
Продюсер
Лидер (Broker 1)
Фолловер 1 (Broker 2)
Фолловер 2 (Broker 3)
Produce(record, acks=all)append(record) → LEO++FetchRequest(offset=LEO₁)FetchResponse(records, HW)FetchRequest(offset=LEO₂)FetchResponse(records, HW)HW = min(ISR LEOs) — продвижениеProduceResponse(offset, error=None)

ISR: In-Sync Replicas — реплики в синхронизации

ISR (In-Sync Replicas) — это множество реплик, которые “достаточно синхронизированы” с лидером. Определение “достаточно”: реплика находится в ISR, если она запрашивала данные у лидера в пределах последних replica.lag.time.max.ms миллисекунд (по умолчанию 30 секунд).

Если фолловер перестал запрашивать данные (брокер упал, сеть пропала, GC pause) — через 30 секунд он выбывает из ISR. Лидер обновляет список ISR в metadata log через KRaft-контроллер.

Список ISR для партиции виден в выводе kafka-topics.sh --describe:

Topic: orders  Partition: 0  Leader: 1  Replicas: 1,2,3  Isr: 1,2,3
#                                                          ^^^^^^^^^^^^
#                                                          ISR = все три реплики

Если брокер 2 упал:

Topic: orders  Partition: 0  Leader: 1  Replicas: 1,2,3  Isr: 1,3
#                                                          ^^^^^^^
#                                                          Брокер 2 выбыл из ISR

LEO: Log End Offset — граница каждой реплики

LEO (Log End Offset) — следующий свободный offset в реплике. Если реплика содержит записи 0..99, её LEO = 100.

LEO у каждой реплики свой. Лидер знает LEO всех фолловеров из их FetchRequest-запросов. После получения запроса FetchRequest { offset: 95 } лидер знает: у этого фолловера LEO = 95 (он прочитал всё до offset 95).


High Watermark: граница безопасного чтения

High Watermark (HW) — это minimum LEO среди всех реплик в ISR. Потребители могут читать записи только до HW.

Формально: HW = min(LEO_leader, LEO_isr_replica_1, LEO_isr_replica_2, ...)

Зачем это нужно? Запись, которую прочитал потребитель, должна быть гарантированно не потеряна при смене лидера. Если потребитель прочитал offset 99, но фолловер реплицировал только до offset 95 — и в этот момент лидер упал, новым лидером стал фолловер с LEO=95 — потребитель увидит данные, которых нет в новом лидере. Это разрыв консистентности.

HW предотвращает это: потребитель читает только то, что гарантированно есть у всех ISR-реплик.

LEO, HW и Consumer Read Boundary

Leader (Broker 1)

Leader (Broker 1): LEO = 103. Принял записи 100, 101, 102. Ждёт подтверждения от ISR-реплик.
FetchResponse: records 100-102

Follower 1 (Broker 2)

Follower 1 (Broker 2): LEO = 101. Получил записи 100, ещё не реплицировал 101 и 102. Отстаёт от лидера.
FetchResponse: records 100-102

Follower 2 (Broker 3)

Follower 2 (Broker 3): LEO = 103. Полностью синхронизирован с лидером.
LEO Leader = 103Лидер имеет все записи до offset 102 включительно. Следующий offset для записи = 103.
LEO Follower 1 = 101Follower 1 реплицировал только до offset 100. Пока что его LEO = 101.
LEO Follower 2 = 103Follower 2 реплицировал все записи. LEO = 103, в синхронизации с лидером.

HW = min(103, 101, 103) = 101

High Watermark = min(103, 101, 103) = 101. Потребители могут читать только до offset 100 включительно. Записи 101 и 102 ещё не 'committed' с точки зрения потребителей.

Consumer: читает 0..100

Consumer: читает только до HW-1 = offset 100. Записи 101 и 102 невидимы до тех пор, пока Follower 1 их не реплицирует и HW не сдвинется.

HW продвигается вперёд: когда фолловер реплицирует очередную запись и присылает FetchRequest с обновлённым LEO, лидер пересчитывает HW. Если все ISR-реплики дошли до LEO=103, HW сдвигается до 103, и потребители могут читать записи 101 и 102.


acks: гарантии подтверждения для продюсера

Параметр acks (acknowledgements) определяет, от скольких реплик продюсер должен получить подтверждение перед тем, как считать отправку успешной:

acks=0 — продюсер не ждёт никакого подтверждения. Fire-and-forget. Максимальная скорость, нулевые гарантии. Даже если брокер упал в момент записи — продюсер об этом не узнает. Использовать только для метрик, логов, где потеря допустима.

acks=1 — продюсер ждёт подтверждения от лидера. Лидер записал в свой лог — ОК. Фолловеры могут отставать. Если лидер упадёт до репликации — данные потеряны. Это значение по умолчанию до Kafka 3.0; в Kafka 4.0 — acks=all.

acks=all (или acks=-1) — продюсер ждёт подтверждения от всех ISR-реплик. Лидер отвечает только после того, как все ISR-реплики записали и подтвердили. Максимальная надёжность.

from kafka import KafkaProducer

producer = KafkaProducer(
    bootstrap_servers=['localhost:9092'],
    acks='all',              # ждать всех ISR реплик
    retries=10,              # повторять при временных сбоях
    retry_backoff_ms=100     # 100ms между попытками
)

min.insync.replicas: нижняя граница ISR

Вот ловушка: acks=all требует подтверждения от всех реплик в ISR. Если ISR содержит только лидера (все фолловеры выбыли) — acks=all требует подтверждения только от лидера. Это эквивалентно acks=1.

Решение: min.insync.replicas (min.isr) — минимальное число реплик в ISR, при котором лидер принимает запись. Если ISR меньше min.isr — лидер отклоняет ProduceRequest с NotEnoughReplicasException.

# Настройка на уровне топика
kafka-topics.sh --alter \
  --topic orders \
  --config min.insync.replicas=2 \
  --bootstrap-server localhost:9092

# Или в server.properties (глобальный default)
min.insync.replicas=2

Рекомендуемая конфигурация для production (RF=3):

# Гарантии durability
acks=all
min.insync.replicas=2

Это означает: запись считается успешной, только если она записана минимум на 2 из 3 реплик (лидер + хотя бы 1 фолловер). Кластер переживает потерю 1 брокера без потери записанных данных.

WARNING

Если ISR сжимается до 1 реплики (лидер) и min.insync.replicas=1 (default) — acks=all требует только одного подтверждения, что неотличимо от acks=1. Устанавливайте min.insync.replicas=2 для топиков с требованиями надёжности, независимо от acks. Без этого гарантии durability иллюзорны.


Eligible Leader Replicas (ELR): предварительный просмотр Kafka 4.0

Kafka 4.0 вводит концепцию ELR (Eligible Leader Replicas) — реплик, которые квалифицированы стать лидером даже при выходе за пределы ISR.

Классическая проблема: если все ISR-реплики потеряны (например, все 3 брокера перезагрузились), Kafka отказывается выбрать нового лидера из оставшихся реплик (они не в ISR) — это защита от потери данных. Администратор должен вручную разрешить “unclean leader election”. ELR расширяет множество кандидатов: реплики, которые были в ISR незадолго до сбоя, остаются “eligible” для election на короткий период. Это уменьшает необходимость в ручном вмешательстве при массовых перезагрузках.

ELR — preview в Kafka 4.0, полная GA запланирована в 4.x.


Ключевые выводы

  1. ISR = реплики, синхронизированные с лидером в пределах replica.lag.time.max.ms. Лидер всегда в ISR.
  2. LEO (Log End Offset) — next offset для записи, уникален для каждой реплики.
  3. HW (High Watermark) = min(LEO всех ISR-реплик). Потребитель читает только до HW.
  4. acks=all + min.insync.replicas=2 — правильная комбинация для production durability.
  5. Если ISR = 1 и min.isr = 1 — acks=all эквивалентен acks=1. Это ловушка.
Проверка знанийKnowledge check
High Watermark партиции = 500. Продюсер только что записал сообщение с offset 501 (LEO лидера = 502). Один из двух фолловеров реплицировал до offset 501 (LEO = 502), второй — только до 499 (LEO = 500). Какие записи может читать потребитель?
ОтветAnswer
Потребитель может читать только до HW - 1 = 499 включительно. HW = min(502, 502, 500) = 500. Записи 500 и 501 недоступны для чтения, пока второй фолловер не реплицирует их и HW не сдвинется. Это механизм защиты консистентности: даже если продюсер получил acks=all от двух ISR-реплик, потребитель не видит записи, пока они не гарантированы на всех ISR-репликах.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 5. Партиция имеет Leader (Broker 1, LEO=200), Follower 1 (Broker 2, LEO=200), Follower 2 (Broker 3, LEO=195). Все три реплики в ISR. Чему равен High Watermark, и какие записи видны потребителю?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 6