Всем привет! На связи Антон Воробьёв — архитектор в Альфа‑Банке. Некоторое время назад у нас появился нативный порт C‑клиента librdkafka, который сделали наши коллеги из компании BTC. Чтобы порт работал быстрее, мы допортировали последнюю на тот момент версию librdkafka 2.3, разобрались с оптимизациями под нашу специфичную ОС, починили все юнит‑автотесты в её составе, сделали свои, и внедрили в июне 2024 года. На сегодня порт отправляет ежедневно 100 миллионов сообщений в 50+ топиков и суммарно в сотни партиций, примерный объём — 200 ГБ в неделю.
Плотно поработав с Apache Kafka мы собрали список типичных ошибок, которые появляются во время проектирования и разработки продюсеров и консьюмеров. Эти ошибки не зависят от используемой платформы, могут встретиться где угодно и могут быть совершены любым участником, как со стороны процесса приёма, так и передачи.
Если вы уже давно работаете с Apache Kafka, то много нового для себя не узнаете. Но если вы не так давно начали изучать Apache Kafka и столкнулись с неправильным использованием ключа партицирования, сайзингом топика, с ошибками в параметрах топика, с нарушением идемпотентности и тому подобное, то статья сэкономит вам время и силы.
Начнём с простого.
№ 1. Неправильное использование ключа партицирования
Ошибка влечёт за собой проблемы, связанные с консистентностью данных и производительностью.
Упрощённо, ключ партицирования в Apache Kafka можно представить как атрибут сообщения, который определяет в какую партицию топика оно будет записано. Ключ будет частью гарантии того, что связанные сообщения будут обработаны в правильном порядке. Ключ позволяет равномерно распределять данные по партиции и обеспечивать атомарность операции при работе с транзакциями.
Экономия на правильной настройке ключей выливается в дальнейшие доработки. На моей памяти был один показательный случай, когда одна команда решила упростить свою систему и зафиксировать ключ партицирования на константу — решили не разбираться, потому что топик и партиция в единственном экземпляре.
Неожиданно произошел рост нагрузки, на что добавили партиций и, как следствие, консьюмеров. Но возникла проблема — из‑за неверного, а точнее, фиксированного ключа партицирования, все сообщения попадали только в одну партицию. А так как к партиции может быть привязан только один консьюмер, то система не масштабировалась и осталась фактически однопоточной.
Чтобы избежать подобных ошибок:
Стоит разобраться в сути ключа партиции и использовать его осознанно.
Будьте аккуратнее с пустыми или null‑ключами: когда вы его не используете, клиент Kafka отправляет сообщения в партиции рандомно.
Используйте стабильные и уникальные идентификаторы ключа, например, Customer ID.
№ 2. Неверный сайзинг топика
Сайзинг топика — это процесс, где вы определяете оптимальное количество партиций, политики хранения и другие параметры для топика. Сайзинг топика необходим, чтобы избежать неожиданных сюрпризов на бою.
Самое главное, зачем нужен сайзинг, так это для того, чтобы ваши данные хранились в необходимом объёме и необходимое количество времени.
Kafka можно представить как бесконечный пергамент, на который пишутся сообщения, а хвост постоянно подрезается. С подрезанным хвостом Kafka поступает абсолютно беспощадно — если вы превышаете лимиты по сроку или объёму хранения, то автоматически теряете хвост данных.
Для примера расскажу про случай в проде. Как‑то на январских праздниках с 7:00 до 7:02 утра одна система отправила 60 000 сообщений для списания комиссии. Но ответа не последовало, проводки не прошли, а сообщения пропали бесследно.
В чём причина? Оказалось, что из‑за регламентных работ задержали запуск консьюмера и он запустился в 7:03 вместо 7:00. Политика по объёму была настроена на 10 Мб и когда объём был превышен, брокер удалил старые сообщения, захватив часть новых, пока консьюмер не работал.
Если бы консьюмер запустился в 7:00, как и планировалось, то заблокировал бы этот сегмент данных и не дал бы Kafka его удалить. Но консьюмер не работал и Kafka ликвидировала сегмент в течение нескольких секунд.
После этого консьюмер пытался читать партицию с последней запомненной точки, но не смог этого сделать, потому что offset уже не валиден (его подрезали). Соответственно, он убежал в конец партиции (присутствовали такие настройки) и часть важных сообщений была потеряна.
Вывод: правильно настраивайте параметры хранения топика, иначе можете столкнуться с чем‑то подобным.
Чтобы избежать ошибки…
-
Не привязывайте алгоритмы к числу партиций:
Слишком малое количество партиций ведет к ограничению параллелизма.
Излишнее партицирование тратит ресурсы процессора впустую: следуйте интеграционной Kafka по количеству партиций.
-
Важно рассчитывать параметры политик хранения (retention). Количество партиций должно быть достаточно для минимального горизонтального масштабирования:
retention.bytes должен быть достаточной глубины хранения,
retention.bytes в идеале должен позволять хранить все сообщения за период retention.ms,
отслеживайте распределение сообщений по партициям.
Также стоит учитывать возможный «взрывной» краткосрочный рост нагрузки при инициирующих отправках (первичная или повторная после сбоя). Естественно, в разумных рамках, которые обычно зависят от SLA консьюмеров, потому что если вы в текущий топик отправите очень большое количество сообщений, а SLA консьюмер такой, что читает сообщения очень медленно, то вы просто приостановите текущую онлайн‑работу на некоторое время. Следовательно, здесь нужно искать баланс или выделить отдельные топики для массовых переотправок.
№ 3. Ошибки в параметрах топика
С неймингом некоторых параметров происходит интересная ситуация. Дело в том, что названия параметров совершенно не соответствуют их сути. Для примера рассмотрим параметр max.message.bytes. На первый взгляд кажется, что он содержит максимальный размер одного сообщения.
Но это не так. Параметр связан с ProduceRequest — сообщением‑контейнером, которое продюсер отправляет на ноду брокера. В сообщении может содержаться много различных батчей (пакетов сообщений), направляемых в различные партиции и топики. А max.message.bytes — это предельный размер одного из этих батчей внутри ProduceRequest.
Но и это не всё! Если max.message.bytes — это настройка со стороны топика, то со стороны клиента у нас есть batch.size. Это параметр, указывающий максимальный размер батча в байтах, который можно формировать данному продюсеру в конкретную партицию топика.
Два данных параметра связаны важным условием: batch.size не должен превышать max.message.bytes. Если превысить, то можно увидеть сообщение Message size too large.

К сожалению, такой случай в проде у нас тоже был один раз из‑за некорректных настроек топика.
Теперь тема посложнее.
№ 4. «Идемпотентность есть гарантия порядка»
Откуда пошло убеждение, что «идемпотентность = порядок»? Как мне кажется, из статей двухминуток на тему «Как нам сохранить порядок сообщений в Kafka?». В целом там пишут правильные вещи, но не делают акцент на скоупе, на области действия.
Объясню: когда у сервиса есть идемпотентность и он делает ретраи, то вы получаете реализацию семантики exactly‑once (один раз отправили — один раз доставили):
Представим, что сеть «моргнула», продюсер отправил пачку сообщений, не получил ответа о её доставке и произвёл отправку снова.
Если сообщение ранее получено и обработано брокером, то он просто отбросит вторую пачку как дублирующее сообщение. Если же первая пачка сообщений не была получена, то обработает её.

Вы можете спросить, а причём здесь вообще порядок? Где он здесь?
Дело в том, что включение механизма идемпотентности (enable.idempotence=true) автоматически ведёт к трём условиям.
acks=-1. Означает, что ответ продюсеру от брокера отдаётся только после подтверждения фиксации сообщения как минимум по количеству in‑sync‑реплик. Например, наш общебанковской кластер Kafka состоит из трёх узлов, и
min.insync.replicas = 2. Это значит, что минимум две ноды должны ответить о том, что сообщение принято и обработано, и только после этого можно сказать, что пачка доставлена.Ретраи стремятся к бесконечности:
retries = INT_MAX.max.in.flight.requests.per.connectionне выше 5. Это необходимо, чтобы продюсер не отправлял одновременно более 5 батчей без ответа (доставлены, не доставлены, ошибка доставки). Число 5 взято из‑за того, что Kafka использует окно последовательности (sequence window) именно такого размера для отслеживания корректности порядка следования. Размер фиксирован — прибит гвоздями. Это внутренний механизм Kafka, с которым ничего нельзя поделать. Как следствие, получается, что в этом окне у вас не должно быть батчей, идущих не по порядку, иначе, к сожалению, мы увидим ошибку доставки.

Кажется, можно сделать вывод, что всё это нам не позволяет нарушать порядок? Ведь у нас нет дубликатов, а значит, мы ничего не теряем, а консьюмер принёс сообщение один раз и в необходимой последовательности. Никаких проблем нет?
Но они есть:
Дело в том, что гарантии порядка работают и даются только в рамках одной партиции одного экземпляра активного продюсера. Два продюсера не могут гарантировать ни идемпотентность, ни порядок.
По нашим нефункциональным требованиям продюсеры и консьюмеры могут быть в любой момент перезапущены с сопровождением: могут быть перезапущены с текущей точки, могут быть перезапущены с точки в прошлом (с точки системного журнала в прошлом текущего опердня).
При возникновении инфраструктурных проблем сообщение может оказаться не доставленным, и тогда оно также выпадает из этих гарантий. Надо знать, что с ним дальше делать.
Кроме того, producer exactly‑once semantic не гарантирует порядок прочтений то есть должен быть ещё и consumer exactly‑once semantic.
Установка
max.in.flight.requests.per.connection = 1без consumer EOS тоже не поможет сохранить идеальный порядок (и идемпотентность тоже).
Теперь немного про вторую сторону вопроса…
№ 5. Неприятности, связанные с консьюмерами и консьюмер‑группами
Что такое консьюмер‑группа (consumer‑группа)? Это механизм для горизонтального масштабирования потребления данных из топиков.
Технически это специальный топик, который называется consumeroffsets, с большим количеством партиций (обычно 40+). В топик консьюмеры пишут сообщения для сохранения позиции чтения (своего оффсета).
Ключом сообщения является склейка из имени консьюмер‑группы, топика и номера партиции → (group_id, topic, partition). В значении же хранится оффсет, метка времени, и метаданные, которые меняются от версии к версии →(offset, timestamp, metadata).
Есть два неприятных момента, связанных с консьюмер‑группами.
Важно понимать, что у топика может быть много консьюмер‑групп. Технически возможно читать без использования консьюмер‑групп, но тогда придётся самостоятельно реализовывать специфическую логику управления этим курсором.
Если допущены ошибки в этих алгоритмах, можно перегрузить брокер, что вызовет проблемы у других потребителей. Поэтому у нас в банке с Kafka запрещено работать без консьюмер‑групп.
А как консьюмеры управляют своими оффсетами? Есть определённые хитрости, но всё сводится к тому, что консьюмеры могут писать свои оффсеты не на каждое сообщение — могут писать, а могут и не писать. Они обычно делают это в течение какого‑то интервала времени или через какое‑то количество сообщений.
Почему? Потому что коммит — очень медленная операция.
В позапрошлом году мы проводили некоторые технические тесты и выявили, что разрыв между асинхронным коммитом раз в 5 секунд (автокоммит) и синхронным коммитом на каждое сообщение разница составляет порядка 45 раз (прочитали в 45 раз медленнее) по астрономическому времени.
Консьюмеры работают с оффсетами по‑разному:
Есть автоматический коммит оффсета раз в 5 секунд (
enable.auto.commit=true).Есть ручной коммит, когда в коде вызывают определённую функцию: это может быть как синхронный вызов, так и асинхронный. Синхронное (commitSync) ручное управление — блокирующий вызов, оффсет сохранится только после успешной обработки брокером. Асинхронное (commitAsync) ручное управление — нет гарантии, что следующий коммит не перезапишет предыдущий по ошибке.
Также существует смешанный режим «синхронно‑асинхронно», когда оба типа можно использовать одновременно.
Самое важное, что необходимо помнить — консьюмер может упасть или перезапуститься в любой момент времени и не успеть закоммитить свой оффсет коммитов, а коммит оффсета при определённых проблемах может быть отвергнут координатором: брокер его отвергнет и вы потеряете свою текущую позицию.
Гарантий, что вы 100% никогда не потеряете свой оффсет в такой системе нет.
Поэтому при рассмотрении решения бизнес‑задачи перед нами появляется дилемма — когда и как консьюмер будет выполнять коммит оффсета.
До или после обработки сообщений?
Синхронно, асинхронно или смешанно?
Фактически, это выбор между скоростью и надежностью. Мы сами выбираем надежность. Поэтому в АБС Equation для максимального соответствия семантике поведения IBM MQ коммит делается на каждое сообщение. Мы используем самый медленный вариант, тем самым достигаем гарантию at most once, потому что опасаемся повторных обработок.
Был интересный случай в проде, связанный с консьюмингом. Дело было так: система получала батч сообщений в количестве 300 штук, и на каждое полученное сообщение вызывала некий REST‑сервис внешней системы. Причём нужно было это делать специально с очень низким RPS — 10–20 сообщений в секунду. Всё шло нормально, но при старте консьюмера заметили, что он всё получает и получает эти 300 сообщений и раз за разом вызывает внешний сервис с одними и теми же данными. Зациклился.

Произошло то, что называется ложная ребалансировка.
Дело в том, что по архитектуре консьюмеров они должны отправлять координатору консюмер‑групп сообщения хартбиты (heartbeat). Если координатор их не получает, то считает консьюмер мёртвым. По документации Kafka эти хартбиты отправляются только во время полинга сообщений — так спроектированы клиентские библиотеки.

Случилось так, что обработка 300 сообщений заняла много времени, что превысило тот самый тайм‑аут сессии. И тогда:
координатор консьюмер‑групп посчитал консьюмер мёртвым,
когда консьюмер пытался закоммитить и отправил коммит на брокер, тот его отверг, потому что консьюмер считался мёртвым,
Kafka‑клиент переподключился,
получил заново стартовый офсет и начал всё заново,
получил старый номер,
начал отправлять сообщения повторно,
повторить.
Этот цикл повторялся много раз.
В Java Kafka Client есть нерекомендуемый способ как отправить heartbeat вне полинга, но в librdkafka и его врапперах для других ЯП (Go/Rust/Python/…) такой возможности нет.
№ 6. Игнорирование ребалансировки
Не игнорируйте ребалансировку, иначе всё может закончиться неприятными последствиями, которые долго и дорого исправляются из‑за асинхронности.
В любой момент времени консьюмер может не просто упасть или быть остановленным сопровождением, а:
перестать читать партицию,
начать читать новые партиции,
начать читать чужую партицию, потому что координатор вдруг решил ему её передать.
Если вы не коммитите оффсет каждый раз, то партиция, переехав на новый консьюмер, начнёт читать с последнего закоммиченного оффсета. Но, если предыдущий консьюмер не успел закоммититься, то начнёт с некоторой позиции в прошлом и успеет переобработать часть сообщений от предыдущего консьюмера. Если не учитывать этот момент, можно получить неприятный сайд‑эффект.
Чтобы избежать проблем с консьюмингом, применяйте несколько стандартных методов.
Отключите автокоммиты и используйте ручные или смешанные режимы управления.
В обязательном порядке обрабатывайте специальные callback, которые есть в библиотеках, чтобы как можно точнее фиксировать свой коммит.
Идемпотентная обработка — используйте внешние хранилища для реализации consumer exactly one семантики. Вы должны быть готовы к обработке повторных сообщений.
Минимизируйте продолжительность ребалансировки (
session.timeout.ms+heartbeat.timeout.ms). Дело в том, что сама ребалансировка уже имеет два алгоритма. Первый алгоритм называется Stop the World, когда координатор может полностью остановить консьюминг ваших консьюмеров и перебалансировать их, раздав новые партиции (обычно происходит при подключении или удалении каких‑то консьюмеров в эту группу). Второй алгоритм останавливает не все консьюмеры, а только часть, связанную с перебалансируемыми партициями.
Проблема в том, что если что‑то случится с процессом ребалансировки, например, возникнут проблемы сетевого обмена или один из консьюмеров упадёт во время ребалансировки, то все остановленные консьюмеры будут находиться в зависшем состоянии (ничего не делать) до тех пор, пока координатор не сочтёт пропавший консюмер мёртвым. Если поставить тайм‑аут и heartbeat.timeout.ms очень большим, например, 5 минут, то всё это время консьюмеры будут ожидать ответа.
№ 7. Игнорирование обработки ошибок и Dead Letter Queue
Этот пункт — про ситуацию, когда одно «ядовитое» сообщение кладёт на лопатки всю очередь. Ошибка приводит к зависанию консьюмеров и потере сообщений при возникновении «плохих» данных.
При обработке сообщений из Kafka неизбежно возникают ситуации, когда сообщение не может быть обработано: некорректный формат, ошибка бизнес‑логики, недоступность внешнего сервиса. Если не предусмотреть механизм обработки таких случаев, консьюмер может застрять в бесконечном цикле, пытаясь обработать одно и то же проблемное сообщение.
Представьте: консьюмер берёт сообщение, пытается обработать — ошибка. Оффсет не закоммичен, на следующем poll прилетает то же самое. И снова ошибка. И снова. Консьюмер крутится на месте, а за ним копится очередь из нормальных сообщений, до которых он никогда не доберётся.
Что с этим делать?
Retry с экспоненциальной задержкой. Для временных ошибок (недоступность БД, таймаут внешнего сервиса) имеет смысл повторить обработку через некоторое время. Но важно ограничить количество попыток.
Dead Letter Queue (DLQ). Когда сообщение не может быть обработано после N попыток, оно отправляется в отдельный топик — очередь «мёртвых писем». Это позволяет консьюмеру продолжить работу, а проблемные сообщения можно проанализировать позже.
Skip и логирование. Для некритичных сообщений можно пропустить обработку и залогировать ошибку. Но это должно быть осознанное решение.
Практический пример из продакшена — одна из наших систем получала JSON‑сообщения и десериализовала их. Однажды продюсер изменил формат поля даты с «YYYY‑MM‑DD» на «DD.MM.YYYY» без уведомления. Консьюмер начал падать на каждом сообщении с ошибкой парсинга. Так как DLQ не был настроен, вся обработка остановилась на несколько часов, пока проблема не была обнаружена.
Рекомендации:
Всегда настраивайте DLQ для критичных топиков.
Ограничивайте количество ретраев.
Логируйте ошибки с достаточным контекстом для диагностики.
Мониторьте размер DLQ и настраивайте алерты.
№ 8. Неправильная настройка acks и гарантии доставки
Параметр acks — это, по сути, ваш ответ на вопрос «насколько я готов рискнуть данными ради скорости». И тут часто ошибаются в обе стороны: ошибка ведёт к потере сообщений или неоправданному снижению производительности.
Параметр acks продюсера определяет, сколько подтверждений от брокеров должен получить продюсер, чтобы считать сообщение успешно записанным. Это ключевой параметр, влияющий на баланс между надёжностью и производительностью.
Есть три значения acks:
acks=0. Продюсер не ждёт никакого подтверждения от брокера. Максимальная производительность, но сообщение может быть потеряно при падении брокера. Используется для метрик, логов, данных, потеря которых некритична.
acks=1. Продюсер ждёт подтверждения только от лидера партиции. Если лидер упадёт до репликации, сообщение будет потеряно. Компромиссный вариант для многих сценариев.
acks=all (или -1). Продюсер ждёт подтверждения от всех синхронизированных реплик (количество определяется параметром min.insync.replicas). Максимальная надёжность, но минимальная производительность.
При acks=all важен параметр топика min.insync.replicas. Он определяет минимальное количество реплик, которые должны подтвердить запись. Если в кластере из 3 нод min.insync.replicas=2, то сообщение считается записанным только после подтверждения от лидера и одной реплики.
У нас был случай: команда поставила acks=all и была уверена, что данные в безопасности. Но min.insync.replicas оставили на единице. По факту acks=all превратился в acks=1 — для подтверждения хватало одного брокера. Пока кластер работал штатно, всё было прекрасно. Стоило потерять лидера — и вместе с ним ушли данные. Самое обидное: ребята были уверены, что у них максимальная надёжность. Формально — да, по факту — нет.
Рекомендации:
Для финансовых и критичных данных: acks=all +
min.insync.replicas ≥ 2.Для метрик и логов: acks=1 или acks=0.
При acks=all всегда проверяйте
min.insync.replicas.Учитывайте, что acks=all увеличивает latency записи.
№ 9. Отсутствие мониторинга и алертинга
Ошибка приводит к обнаружению проблем слишком поздно, когда бизнес уже пострадал.
Однажды в пятницу вечером один из консьюмеров начал работать медленнее из‑за деградации внешнего сервиса. Лаг начал расти, чего никто не заметил, так как алертов не было. В понедельник утром обнаружили, что накопилось больше 2 миллионов сообщений с критичными финансовыми операциями. Команда потратила весь день на разгребание очереди.
Kafka — это распределённая система, и без правильного мониторинга проблемы могут накапливаться незаметно. К моменту, когда пользователи начнут жаловаться, ситуация может быть уже критической.
Метрика номер один — consumer lag. Если лаг растёт, значит консьюмер не успевает. Всё остальное — throughput, error rate, диск на брокерах — вторичные индикаторы, которые помогают понять, почему именно лаг растёт. Но если у вас нет алерта на лаг, считайте, что мониторинга нет
Настройка алертов:
Consumer lag > порогового значения.
Consumer lag time > SLA обработки.
Error rate > допустимого уровня.
Disk usage на брокерах > 80%.
Offline partitions.
Рекомендуемые инструменты:
Kafka Manager / CMAK — управление кластером и базовый мониторинг.
Prometheus + Grafana — метрики и визуализация (JMX exporter для брокеров, client metrics для продюсеров/консьюмеров).
Burrow — специализированный инструмент для мониторинга consumer lag.
Kafka Exporter — экспорт метрик Kafka в Prometheus.
№ 10. Неправильная работа со схемами данных (Schema Registry)
Этот пункт — про ситуации, когда продюсер и консьюмер живут в разных командах и развиваются с разной скоростью. Ошибка приводит к несовместимости версий и сбоям в продакшене.
В микросервисной архитектуре продюсеры и консьюмеры развиваются независимо. Без управления схемами данных изменение формата сообщения может привести к падению консьюмеров.
Решение проблемы — Schema Registry — это централизованное хранилище схем данных (обычно Avro, Protobuf или JSON Schema). Оно позволяет:
хранить истории версий схем,
проверять совместимость новых версий с предыдущими,
сериализовать/десериализовать сообщения по идентификатору схемы.
Кто обновляется первым — продюсер или консьюмер? От этого зависит тип совместимости. Если сначала обновляете консьюмер — это BACKWARD: новая схема умеет читать старые данные. Если сначала продюсер — FORWARD. Если хотите не думать о порядке — FULL, но это самый строгий вариант.
Типичные ошибки:
Удаление обязательного поля. Старые консьюмеры ожидают это поле и падают.
Изменение типа поля. Новая схема несовместима со старыми данными.
Переименование поля. Фактически, удаление старого и добавление нового поля.
Игнорирование проверки совместимости. Разработчик меняет схему, не проверяя совместимость. В проде падают консьюмеры.
У нас была история: команда продюсера добавила обязательное поле и задеплоилась. Всё работало — у них. А через полчаса посыпались алерты от трёх консьюмеров из других команд: десериализация ломалась на каждом сообщении. Откатывали продюсер, потом заново деплоили — уже правильно: сначала поле как опциональное, потом ждём, пока все консьюмеры обновятся, и только потом делаем его обязательным.
Рекомендации:
Всегда используйте Schema Registry для критичных данных.
Настраивайте проверку совместимости перед регистрацией новой схемы.
Добавляйте новые поля как опциональные, затем обновляйте консьюмеров.
Удаляя поле, сначала сделайте его опциональным, подождите обновления консьюмеров.
Документируйте изменения схем и уведомляйте команды‑потребители.
№ 11. Неправильный выбор компактификации топиков
Компактификация — одна из тех фич Kafka, которые звучат просто, но подкидывают сюрпризы, если не разобраться до конца, как именно она работает. Ошибка приводит к потере данных или неожиданному поведению системы.
Именно так попалась одна из наших команд: использовали compact‑топик для событий транзакций, ключом был ID счёта. Всё работало нормально, пока не пришлось поднимать историю для разбора инцидента. Оказалось, что от каждого счёта осталась только последняя транзакция — всё остальное Kafka вычистила при компактификации. Данные восстанавливали из резервных копий.
Kafka поддерживает два режима удаления данных: retention‑based (по времени или объёму) и compaction (компактификация). Неправильный выбор режима может привести к серьёзным проблемам.
Retention‑based удаление. Стандартный режим — Kafka хранит сообщения в течение определённого времени (retention.ms) или до определённого объёма (retention.bytes). Старые сообщения удаляются независимо от их содержания. Подходит для логов и метрик, Event sourcing (когда важна вся история), событий с ограниченным временем жизни.
Log Compaction. Kafka сохраняет только последнее значение для каждого ключа. Старые значения того же ключа удаляются. Работает только для сообщений с ключом. Подходит для: текущего состояния (current state) например, кэш пользовательских профилей, Change Data Capture (CDC), таблиц конфигурации.
Типичные ошибки:
Compaction для событий без ключа. Если ключ
null, Kafka не может определить, какие сообщения дублируются. Компактификация не работает или работает некорректно.Compaction для event sourcing. Если вам нужна полная история изменений, compaction удалит промежуточные состояния.
Ожидание мгновенной компактификации. Compaction происходит асинхронно. После записи нового значения старое не удаляется мгновенно.
Чтение с начала compact‑топика. Консьюмер может не увидеть все исторические значения, а только последние для каждого ключа.
Когда использовать компактификацию:
Вам нужно только актуальное состояние.
Ключ сообщения — уникальный идентификатор сущности.
История изменений не важна или хранится отдельно.
Когда НЕ использовать компактификацию:
Нужна полная история событий.
Сообщения не имеют ключа или ключ не уникален.
Важен порядок всех сообщений.
Параметры компактификации:
cleanup.policy=compact— включает компактификацию.min.cleanable.dirty.ratio— доля «грязных» сообщений для запуска компактификации.delete.retention.ms— время хранения удалённых значений (tombstone).
Вместо заключения
Apache Kafka — мощный и гибкий инструмент для построения распределённых систем обмена сообщениями. Но, как и любой сложный инструмент, она требует глубокого понимания внутренних механизмов и внимательного подхода к проектированию.
Ошибки, которые мы рассмотрели, объединяет одно: они проявляются не сразу, а в момент нагрузки, сбоя или масштабирования. И именно поэтому они так опасны — система может работать месяцами, пока однажды не «выстрелит» в самый неподходящий момент.
Краткая шпаргалка по предотвращению ошибок:
Ошибка |
Ключевой совет
|
|---|---|
Ключ партицирования |
Используйте осознанно, не фиксируйте на константу |
Сайзинг топика |
Рассчитывайте retention с запасом, учитывайте пиковые нагрузки |
Параметры топика |
batch.size ≤ max.message.bytes, изучайте документацию |
Идемпотентность |
Помните про scope: только один продюсер, одна партиция |
Консьюмер‑группы |
Выбирайте стратегию коммита осознанно, готовьтесь к ретраям |
Ребалансировка |
Обрабатывайте callback'и, минимизируйте время ребалансировки |
Обработка ошибок |
Настраивайте DLQ, ограничивайте ретраи |
acks |
Для критичных данных: acks=all + min.insync.replicas ≥ 2 |
Мониторинг |
Consumer lag, error rate, алерты на все критичные метрики |
Schema Registry |
Проверяйте совместимость схем, обновляйте поэтапно |
Compaction |
Только для current state, не для event sourcing |
Главный вывод: проектируйте систему так, чтобы она корректно обрабатывала не только happy path, но и всевозможные сценарии сбоёв. Потому что в распределённых системах сбои — это не исключение, а норма.
Если статья помогла вам избежать хотя бы одной из описанных ошибок — значит, время на её написание потрачено не зря.
Спасибо за внимание!
Читайте также:
— Не все RPS одинаково полезны: уроки нагрузочного тестирования core‑системы
— Будущее как в «Джетсонах»: как в одном мультфильме кристаллизовались идеи о будущем из прошлого, что определяют наше настоящее
ggo
Забыли добавить Правило №0.
зы
не потому что я не люблю очереди ;)
по аналогии с правилами использования кеширования - Правило №0 "Не используй кеширование"