Событийная модель под нагрузкой: где ломается доставка
Событийная архитектура редко падает из-за самого брокера. Проблемы начинаются на границах: партиции заканчиваются, потребители не успевают за потоком, повторная доставка меняет данные, а ретраи превращаются в лавину.

Событийная система может выглядеть здоровой при сотне сообщений в секунду и начать терять время уже при десятикратном росте потока: растёт consumer lag, группы потребителей перезапускаются, а повторная обработка создаёт дубликаты в базе. Ниже — разбор для архитектуры на Apache Kafka 3.9: что именно происходит под нагрузкой, какие показатели измерять и где заранее поставить ограничители.
Нагрузка упирается не в одно место
В событийной модели есть минимум четыре независимых этапа: производство события, запись в брокер, чтение потребителем и побочные действия — например, запись в PostgreSQL или вызов внешнего API. Пропускная способность системы определяется самым медленным звеном, а не скоростью Kafka как отдельного компонента.
Если производитель отправляет 20 000 событий в секунду, а обработчик стабильно подтверждает только 12 000, отставание будет накапливаться. Пока на диске брокера есть место и не истёк срок хранения, сообщения формально не потеряны. Но бизнес уже видит задержку: заказ получил статус через несколько минут, уведомление ушло после оплаты, а отчёт показывает старые данные.
Главный показатель событийной системы под нагрузкой — не средняя скорость, а время от публикации события до завершения бизнес-обработки.
Как проходит событие под нагрузкой
1. Производитель распределяет поток
Kafka записывает сообщения в партиции топика. Ключ события влияет на выбор партиции: одинаковый ключ обычно удерживает события в одной партиции и сохраняет порядок внутри неё. Это полезно для заказа или аккаунта, но создаёт риск горячей партиции. Если один клиент генерирует непропорционально много событий, свободные партиции не спасут: конкретная партиция станет узким местом.
Проверяйте не только общий throughput, но и распределение записей по партициям. Равномерный объём в среднем может скрывать одну перегруженную партицию.
2. Брокер подтверждает запись
Надёжность зависит от того, когда производитель считает запись успешной. При подтверждении после записи на лидера задержка обычно ниже, но отказ узла может потребовать восстановления с реплик. При ожидании подтверждений от реплик задержка выше, зато уменьшается риск признать сообщение записанным слишком рано.
Для критичных событий нужно заранее определить, что означает «принято»: запись на брокер, запись на несколько реплик или завершение всей бизнес-операции. Это три разных момента, и смешивать их в одной метрике опасно.
3. Потребитель читает быстрее или медленнее входящего потока
Внутри consumer group каждая партиция в конкретный момент назначается одному потребителю. Добавление экземпляров группы помогает только пока число потребителей не превышает число партиций. Десять экземпляров для четырёх партиций не дадут десятикратного ускорения: часть экземпляров останется без работы.
Если обработчик выполняет долгий запрос к внешнему сервису, он может не успеть отправить очередное подтверждение до тайм-аута. Тогда группа начинает перераспределять партиции. Старый экземпляр ещё работает, новый уже получил ту же партицию — одно событие может быть обработано повторно.
4. Побочный эффект завершает обработку
Самая частая ошибка — считать событие обработанным сразу после чтения. Если приложение сначала подтверждает смещение, а потом падает до записи в базу, Kafka больше не выдаст это сообщение группе. Если порядок обратный, сбой после записи, но до подтверждения приведёт к повтору.
Поэтому обработчик должен быть идемпотентным1. Повторная доставка не должна повторно списывать деньги, создавать заказ или отправлять письмо без проверки состояния операции.
Что ломается первым
- Растёт lag. Потребители подтверждают сообщения медленнее, чем производители публикуют новые.
- Появляется горячая партиция. Один ключ или неудачная стратегия распределения перегружает часть топика.
- Начинаются ребалансы. Долгая обработка, нестабильная сеть или частые рестарты меняют состав группы и временно останавливают чтение.
- Ретраи усиливают аварию. Недоступный внешний сервис получает ещё больше запросов, потому что каждый сбой запускает немедленную повторную попытку.
- Заполняется локальная очередь. Приложение принимает работу быстрее, чем успевает завершить, и заканчивает память или соединения с базой.
Три стратегии доставки и их цена
Термины «ровно один раз» и «без потерь» нельзя использовать без уточнения границ. Kafka может участвовать в транзакционной обработке своих записей, но запись в Kafka и вызов внешнего платёжного API не становятся одной атомарной операцией автоматически.
| Стратегия | Что происходит при сбое | Цена | Где применять |
|---|---|---|---|
| At-most-once | Сообщение подтверждается до побочного действия; возможна потеря | Минимальная задержка и простая схема | Метрики, телеметрия, некритичные обновления |
| At-least-once | Сначала выполняется действие, затем подтверждение; возможен повтор | Нужна идемпотентность и защита от дублей | Заказы, изменения состояния, интеграции |
| Транзакционная обработка | Связанные операции внутри поддерживаемой транзакционной границы фиксируются согласованно | Сложнее отладка, выше задержка и требования к конфигурации | Цепочки Kafka-to-Kafka, где граница транзакции ясна |
Для связки «Kafka плюс реляционная база» часто применяют паттерн outbox: приложение записывает изменение и событие в одну транзакцию базы, а отдельный процесс публикует события в брокер. Это уменьшает риск расхождения между состоянием заказа и отправленным сообщением, но добавляет очередь outbox, мониторинг и отдельную задачу повторной публикации.
Сноска. Идемпотентность — свойство операции, при котором повторный запуск с теми же данными не меняет результат после первого успешного выполнения.
Что измерять, а не угадывать
Без измерений нельзя честно сказать, выдержит ли система рост в два или десять раз. Тест должен фиксировать условия: число брокеров, размер сообщения, количество партиций, replication factor, число экземпляров потребителя, тип дисков, объём базы и долю ошибок внешних API.
- Запишите базовый поток. Измерьте скорость публикации, скорость успешной обработки и p50, p95 и p99 задержки от timestamp события до завершения обработчика.
- Проверьте распределение. Снимите объём и lag по каждой партиции, а не только среднее значение по топику.
- Увеличивайте поток ступенчато. Поднимайте нагрузку небольшими шагами и ждите стабилизации. Ищите точку, после которой lag растёт без возврата к исходному уровню.
- Добавьте отказ. Отключите один экземпляр потребителя, сделайте внешний API медленным, заполните часть диска тестового брокера. Замерьте время восстановления и количество повторов.
- Проверьте дубликаты. Сравните число входящих событий с числом уникальных бизнес-операций. Для заказа уникальность должна определяться бизнес-ключом, а не только offset.
В отчёте нужны не только средние значения. Средняя задержка в 100 миллисекунд ничего не говорит о пяти минутах ожидания для одного процента заказов. Для пользовательских операций обычно важнее p95 или p99, а для потоковой аналитики — максимальная задержка и скорость восстановления после отставания.
Граничные случаи: где схема перестаёт масштабироваться
Партиций меньше, чем параллельных обработчиков
Увеличение числа контейнеров не поможет, если партиций мало. Переразбивка топика требует отдельного плана: порядок по ключу, объём данных, распределение нагрузки и совместимость потребителей. Это не операция, которую стоит выполнять во время аварии без теста.
Один ключ требует строгого порядка
Порядок Kafka гарантирует внутри партиции, а не между всеми партициями. Если события одного заказа должны идти последовательно, ключ должен быть выбран так, чтобы они попадали в одну партицию. Цена — ограниченная параллельность для самых горячих ключей.
Внешний сервис медленнее брокера
Брокер может принять поток быстрее, чем платёжный шлюз, CRM или почтовый провайдер. Нельзя лечить это бесконечным увеличением числа потребителей: внешняя система начнёт отвечать ошибками или введёт собственный rate limit. Нужны ограничение параллельности, экспоненциальная задержка между повторами, отдельный поток для временных ошибок и ручной разбор постоянных ошибок.
Сообщение нельзя обработать
Битое событие или ошибка бизнес-данных не должны бесконечно блокировать партицию. После ограниченного числа попыток сообщение отправляют в отдельный поток для разбора, сохраняя исходное событие, причину ошибки и время последней попытки. Автоматический возврат из такого потока без исправления причины только повторит инцидент.
Когда выбирать какую защиту
Если потеря события допустима, проще отделить его от критичного бизнес-потока и выбрать низкую задержку. Если повтор допустим, но потеря нет, используйте at-least-once и проектируйте идемпотентный обработчик. Если требуется согласованная цепочка Kafka-to-Kafka, оценивайте транзакционный режим, но не переносите обещание «ровно один раз» на внешнюю базу или API без отдельного механизма согласования.
Ключевой критерий — не модное название гарантии, а цена сбоя. Для уведомления повтор может быть неприятностью. Для списания денег повтор должен быть невозможен на уровне бизнес-операции или остановлен проверкой уникального идентификатора.
Что делать дальше
Сначала зафиксируйте SLO: например, максимальную допустимую задержку обработки и время восстановления после отказа. Затем добавьте дашборд с throughput, p95/p99 latency, lag по партициям, числом ребалансов, долей повторов и возрастом самого старого необработанного сообщения. После этого проведите нагрузочный тест с замедлением внешней зависимости.
Отдельно проверьте два сценария: падение обработчика после записи в базу и падение до записи. Если оба сценария дают предсказуемый результат, событийная модель уже готова к следующему этапу — настройке ёмкости и бюджета, а не только к демонстрации на штатном трафике.
Защитник прав потребителей: Досудебные претензии на возврат товара со ссылками на ЗоЗПП.
Спросить за 15 ₽Источники: Apache Kafka Documentation 3.9 — Design, Apache Kafka Documentation 3.9 — Consumer Configs
Частые вопросы
Почему consumer lag растёт, хотя Kafka не показывает ошибок?
Поможет ли увеличить число экземпляров потребителя?
Как не допустить повторного списания денег при повторной доставке?
Можно ли гарантировать ровно одну обработку события?
Что делать, если после нагрузки сообщения начали обрабатываться по несколько раз?
Как понять, что пора увеличивать число партиций?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.