Kafka topics, partitions и гарантии порядка

Самая частая ошибка в Kafka - ожидать глобальный порядок сообщений в topic. Kafka гарантирует порядок только внутри одной partition. Если topic разбит на 12 partitions, это 12 независимых логов, которые пишутся и читаются параллельно.

text
topic: orders partition 0: [o-1 created][o-1 paid][o-1 shipped] partition 1: [o-2 created][o-2 cancelled] partition 2: [o-3 created][o-4 created][o-3 paid] Порядок есть внутри каждой линии. Глобального порядка между линиями нет.

Это не недостаток, а компромисс ради масштабирования. Чтобы Kafka могла обрабатывать много данных, она делит topic на partitions. Ваша задача как backend-разработчика - выбрать key так, чтобы нужный вам порядок оказался внутри одной partition.


Topic - имя потока, partition - единица параллелизма

Topic задаёт доменную категорию: orders.events, payments.events, users.changelog. Partition задаёт физическое разделение потока.

Количество partitionsЧто меняется
1Простой порядок во всём topic, но низкий параллелизм.
3Можно читать тремя consumers внутри одной group.
24Больше throughput, но больше файлов, metadata и rebalancing-стоимости.

Партиции нельзя выбирать "на всякий случай бесконечно много". Каждая partition - это логи на диске, replication, metadata в cluster, участие в leader election и rebalance.

Практическое правило: partitions выбирают под ожидаемый throughput, количество consumers и запас роста. Увеличить partitions обычно можно. Уменьшить - нет, только пересоздавать topic или переносить данные.


Keying: как producer выбирает partition

Producer отправляет record с key или без key. Если key есть, partition обычно выбирается хешем:

text
partition = hash(key) % number_of_partitions

Это значит:

  • одинаковый key обычно попадает в одну partition;
  • порядок для одного key сохраняется;
  • разные keys могут попадать в разные partitions;
  • при увеличении количества partitions распределение новых сообщений может измениться.

Последний пункт опасен для production. Если hash(key) % partitions начал давать другую partition, новые события того же key могут поехать в новую partition, пока старые ещё лежат в старой. Между двумя partitions Kafka не даёт порядка, поэтому увеличение partitions для topic с жёстким per-key ordering требует отдельного плана: окно остановки, новый topic, миграция, dual write/read или осознанное принятие риска.

Примеры key:

TopicХороший keyПочему
orders.eventsorder_idВсе события заказа идут последовательно.
users.eventsuser_idСобытия пользователя не перемешиваются.
payments.eventspayment_id или order_idЗависит от нужного порядка.
audit.eventstenant_id:user_idМожно сохранить порядок действий пользователя внутри tenant.

Плохой key - случайный UUID для каждого события, если бизнесу нужен порядок по заказу. Плохой key - константа "all", если вы случайно отправляете весь поток в одну partition и убиваете параллелизм.

Отдельная проблема - hot key. Если 40% событий приходят по одному tenant_id, то одна partition будет отставать, даже если topic имеет 24 partitions и cluster выглядит свободным. Иногда key приходится делать составным: tenant_id:entity_id, provider:currency_pair или другой вариант, который сохраняет нужный порядок, но распределяет нагрузку.


Ordering guarantees

Kafka даёт несколько уровней порядка.

ГарантияРаботает?Условие
Порядок внутри partitionДаRecords записаны в partition последовательно.
Порядок для одного keyОбычно даKey стабильно попадает в одну partition.
Порядок между partitionsНетPartitions независимы.
Порядок после retry producerДа, если правильно настроеноIdempotent producer, bounded in-flight requests.
Порядок после consumer parallel processingНе автоматическиНужна осторожная модель обработки.

Consumer может сломать порядок, даже если Kafka его сохранила. Например, вы читаете partition последовательно, но отправляете сообщения в worker pool, где второе событие обработалось раньше первого. Для событий одного key это может быть критично.

text
Kafka partition: A1 -> A2 -> A3 Worker pool: A2 finished first, A1 still running Результат в БД может стать неконсистентным.

Если порядок важен, обрабатывайте partition последовательно или шардируйте worker pool по key.


Replication factor

Replication factor показывает, сколько копий partition хранит Kafka.

text
topic orders, partition 0, replication.factor=3 Broker 1: leader orders-0 Broker 2: follower orders-0 Broker 3: follower orders-0

Leader принимает read/write для partition. Followers копируют данные с leader. Если broker с leader умер, Kafka выбирает нового leader из актуальных replicas.

Replication factor обычно ставят 3 в production. Это не "магическая защита от всего", а компромисс: можно пережить потерю одного broker без потери доступности при нормальных настройках.


ISR: in-sync replicas

ISR - это replicas, которые не отстали от leader сильнее допустимого. Не все followers автоматически считаются безопасными кандидатами.

text
orders-0 leader: broker-1 offset=1050 follower broker-2 offset=1050 -> ISR follower broker-3 offset=1001 -> out of sync

ISR важен для durability. Если producer требует подтверждения от всех in-sync replicas, Kafka может гарантировать, что запись есть не только на leader.

Но ISR может сжиматься. Если followers не успевают за leader из-за сети, диска или нагрузки, они выпадают из ISR. Тогда cluster становится более хрупким.


Producer acks

acks определяет, когда broker отвечает producer "запись принята".

acksКогда producer получает успехРиск
0Не ждёт broker вообщеМаксимальный риск потери.
1Leader записал recordПотеря возможна, если leader умер до replication.
all / -1Leader и ISR подтвердили записьЛучший durability, выше latency.

Для production-событий обычно выбирают acks=all вместе с min.insync.replicas.


min.insync.replicas

min.insync.replicas говорит, сколько replicas должно быть в ISR, чтобы запись с acks=all считалась успешной.

Типичная production-связка:

text
replication.factor=3 min.insync.replicas=2 producer acks=all

Что это даёт:

  • запись успешна, если её подтвердили минимум две in-sync replicas;
  • если доступна только одна replica, producer получит ошибку;
  • система выбирает отказ записи вместо тихой потери durability.

Это важная мысль: хорошие настройки Kafka иногда ломают запись, чтобы не принять данные в небезопасном состоянии. Backend должен уметь обработать такую ошибку: retry, circuit breaker, outbox, degradation.


Leader/follower и failover

Каждая partition имеет leader. Producer и consumer работают через leader. Followers догоняют leader.

text
Client | v Broker 1 leader for orders-0 | replication +--> Broker 2 follower +--> Broker 3 follower

Если broker 1 падает:

text
Broker 2 becomes leader for orders-0 Producer refreshes metadata Consumer reconnects

В этот момент возможны временные ошибки: NOT_LEADER_OR_FOLLOWER, timeout, disconnect. Нормальный Kafka client умеет обновлять metadata и повторять операции, но ваш код всё равно должен иметь context timeout и понятную обработку ошибок.


Как выбирать partitions

Простой ориентир:

  1. Оцените throughput: сколько MB/s пишем и читаем.
  2. Оцените максимальное количество consumers внутри одной group.
  3. Подумайте о key cardinality: достаточно ли разных keys, чтобы равномерно распределиться.
  4. Заложите рост, но не создавайте тысячи partitions без необходимости.

Пример:

text
orders.events: partitions: 12 replication.factor: 3 min.insync.replicas: 2 key: order_id

Такой topic можно параллельно читать до 12 consumers внутри одной group. События одного заказа будут идти в одну partition.

Для RateDesk событие RateUpdated выглядит простым, но key всё равно является архитектурным решением. Key по currency_pair сохраняет порядок обновлений пары из разных источников хуже, если источники конфликтуют. Key по provider:currency_pair лучше изолирует источники, но downstream должен сам решать, какой provider сейчас главный. Это не задача Kafka, а доменное правило.


Capacity и стоимость partitions

Partitions влияют не только на throughput:

  • больше partitions = больше открытых файлов, metadata и controller work;
  • rebalance занимает дольше, если group владеет тысячами partitions;
  • leader election и recovery становятся тяжелее;
  • слишком много маленьких partitions ухудшают batching и compression;
  • слишком мало partitions ограничивают consumer parallelism и могут создать lag.

Хороший capacity plan связывает expected MB/s, размер record, retention, replication factor, количество consumer groups и допустимый lag. "Поставим 100 partitions на всякий случай" часто так же плохо, как "оставим одну".


Типичные компромиссы

НужноКомпромисс
Глобальный порядок1 partition, но низкий throughput.
Высокий throughputМного partitions, но нет глобального порядка.
Высокая durabilityacks=all, больше latency и возможные ошибки при деградации ISR.
Низкая latencyМеньшие batch и слабее durability-настройки.
Равномерная нагрузкаХороший key, но иногда сложнее сохранить доменный порядок.
Будущий рост partitionsЛегче масштабировать throughput, но можно сломать порядок для key при изменении mapping.

Kafka почти всегда про осознанный баланс. "Поставим 100 partitions, acks=1, потом разберёмся" - плохая стратегия для доменных событий.


Вопросы на собеседовании

  • Где Kafka гарантирует порядок сообщений?
  • Почему одинаковый key обычно попадает в одну partition?
  • Что произойдёт с порядком при worker pool на стороне consumer?
  • Чем leader partition отличается от follower?
  • Что такое ISR?
  • Как связаны replication.factor, acks=all и min.insync.replicas?
  • Почему acks=1 может потерять данные при падении leader?
  • Можно ли уменьшить количество partitions в существующем topic?
  • Почему увеличение partitions может быть опасно для key-based ordering?
  • Как hot key проявится в consumer lag?

Практика

  1. Для topic orders.events выберите key и объясните, какие события должны сохранять порядок.
  2. Нарисуйте схему topic с 6 partitions и consumer group из 4 consumers: кому сколько partitions достанется?
  3. Объясните поведение producer при replication.factor=3, min.insync.replicas=2, acks=all, если жив только один broker.
  4. Придумайте пример, где глобальный порядок важнее throughput, и topic стоит сделать с одной partition.

Интерактивная практика

Quiz+10 XP

Где Kafka гарантирует порядок сообщений?

  • Только внутри одной partition
  • Между всеми partitions одного topic
  • Между всеми topics одного broker
  • Только внутри consumer group, независимо от partition
Predict+15 XP

Что выведет этот код?

go
package main import "fmt" func partitionMapping(sameKey bool) string { if sameKey { return "same" } return "maybe-different" } func main() { fmt.Println(partitionMapping(true)) fmt.Println(partitionMapping(false)) }
Задача+20 XP

Реализуй WriteDecision: при acks=all запись можно принять, только если число in-sync replicas не меньше minISR.