Суть Читайте реальный код producer, конфиг consumer, арифметику partition и лог rebalance, предсказывайте поведение и выбирайте исправление с наибольшим рычагом.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Баги partition диагностируются в коде producer, конфиге consumer и логах rebalance — не в прозе. Прочитайте каждый сниппет, предскажите, что с ним сделает Kafka, и выберите исправление, которое senior-инженер сделал бы первым.
Цель
Отработайте цикл, который вы запускаете в каждом инциденте Kafka: прочитать key producer, конфиг consumer, арифметику partition и лог rebalance, затем взяться за исправление, уважающее порядок и параллелизм, а не замазывающее их.
Сниппет 1 — key producer
// События заказа: created, paid, shipped, cancelled все идут сюдаProducerRecord<String, OrderEvent> record = new ProducerRecord<>("order-events", event.getType(), event);// ^^^^^^^^^^^^^^^^^// key = ТИП события, не id заказаproducer.send(record);
Викторина
Completed
Consumer должен обрабатывать события каждого заказа по порядку (created до cancelled). При таком keyed что произойдёт и как исправить?
Heads-up Порядок гарантирован только внутри partition, никогда на уровне топика. Keyed по типу отправляет 'created' и 'cancelled' одного заказа на разные partition, между которыми нет порядка.
Heads-up Жёсткое задание partition свалило бы всё на один partition (нет параллелизма) и всё равно не связало бы события заказа корректно. Исправление — keyed по сущности (orderId), позволяя partitioner co-locate её.
Heads-up acks=all — это настройка durability про подтверждение репликами; она никак не влияет на то, в какой partition попадёт запись. Разрыв порядка вызван выбором key.
Сниппет 2 — арифметика partition
# Дефолтный partitioner в стиле murmur2: partition = hash(key) % Ndef partition_for(key, N): return hash(key) % N# orderId "A-4711" до и после увеличения partitionpartition_for("A-4711", 6) # -> 2partition_for("A-4711", 12) # -> 8 # тот же key, другой partition!
Викторина
Completed
Топик подняли с 6 до 12 partition на живом. Читая эту арифметику, каково последствие для key A-4711 и почему это нельзя отменить?
Heads-up Существующие записи никогда не перемещаются при увеличении partition. Только будущие записи используют новый modulo, что и раскалывает key между partition 2 и partition 8.
Heads-up hash(key) стабилен, но partition — это hash(key) % N, а N изменился с 6 на 12. Результат modulo меняется для большинства key — здесь с 2 на 8.
Heads-up Количество partition может только расти, никогда уменьшаться. Kafka отказывается сливать partition, потому что нет корректного способа переплести две независимые временные шкалы offset.
Сниппет 3 — конфиг consumer
# Consumer group: payments-processor, 4 инстанса за rolling-деплоемgroup.id=payments-processorsession.timeout.ms=10000heartbeat.interval.ms=3000# group.instance.id НЕ заданpartition.assignment.strategy=org.apache.kafka.clients.consumer.RangeAssignor
Викторина
Completed
Каждый rolling-деплой вызывает многосекундную остановку потребления по всей группе. Читая этот конфиг, в чём причина и какое изменение с наименьшим риском?
Heads-up Снижение session timeout заставит группу объявлять участников мёртвыми быстрее — вызывая БОЛЬШЕ rebalance, а не меньше. Static membership позволяет быстрому перезапуску переподключиться как тот же участник без rebalance.
Heads-up Heartbeat в 3с при session timeout в 10с — нормальное соотношение и не причина остановок при деплое. Остановка — это eager rebalance, отзывающий все partition при каждом перезапуске.
Heads-up CooperativeStickyAssignor существует именно чтобы избежать stop-the-world отзыва, перемещая только те partition, что должны измениться. В сочетании со static membership rebalance при деплое в основном исчезают.
Сниппет 4 — лог rebalance
[Consumer clientId=c3, groupId=payments] (Re-)joining group[Consumer clientId=c3, groupId=payments] Lost previously assigned partitions order-events-2, order-events-5[Consumer clientId=c3] Revoke previously assigned partitions order-events-2, order-events-5... 7 таких циклов за 90 секунд ...[Consumer clientId=c3] Member c3 sending LeaveGroup request due to consumer poll timeout has expired
Викторина
Completed
Читая этот лог — повторяющиеся циклы join/revoke, завершающиеся LeaveGroup по poll-timeout, — каков режим сбоя и каково первое исправление?
Heads-up Consumer достукивается до группы и каждый цикл получает назначение partition — лог показывает успешные join. Проблема в том, что он не может снова сделать poll вовремя, а не в связности.
Heads-up Partition принадлежит ровно одному consumer в каждый момент; нет совместного владения, за которое можно драться. Revoke — это протокол rebalance, реагирующий на повторяющееся выселение c3 по poll-timeout.
Heads-up Количество partition не связано с циклом выселения по poll-timeout. Consumer выкидывают за медленную обработку между poll, поэтому исправление — в каденции poll / размере батча, а не в топике.
Итог
Каждый инцидент с partition читается в коде, конфиге и логах: key producer решает, что остаётся упорядоченным (keyed по сущности, а не по типу события); hash(key) % N означает, что увеличение partition перенаправляет живые key и его нельзя отменить; eager-assignor плюс отсутствующий group.instance.id делает каждый деплой stop-the-world rebalance; а цикл join/revoke, завершающийся poll-timeout, — это rebalance-шторм из-за медленной обработки, а не проблема broker или partition. Сначала читайте key и конфиг, исправляйте структурную причину, затем сверяйтесь с метриками lag и rebalance.