Модуль 4. Тюнинг под нагрузкой и exactly-once в проде
Цель модуля: выжать из кластера цифры вместо мнений — latency против
throughput через linger/batch/compression — и разобраться, когда
exactly-once транзакции Kafka нужны по-настоящему, сколько они стоят и
как их включать в Go.
Перед началом: Модули 1–3, стенд (кластер ×3,
make topic). Трудоёмкость: ~10–12 ч (замеры 6 ч · теория 4 ч · квиз 1 ч).
📖 4.1. Тюнинг продюсера: три ручки и одна кривая
Всё продюсер-тюнинг сводится к одной кривой: латентность ↔ throughput. Три ручки двигают точку на ней:
| Ручка | Что делает | Цена |
|---|---|---|
linger.ms (kafka-go: не выставлять — батч собирается по BatchSize/BatchTimeout) | ждёт соседей для батча | +latency до N мс, −requests, +throughput |
batch.size / BatchSize (байт на партицию) | размер батча | больше = эффективнее сжатие и меньше реквестов |
compression (none/gzip/snappy/lz4/zstd) | сжимает батч ЦЕЛИКОМ | CPU продюсера ↔ сеть/диск; zstd — лучший ratio у тяжёлых payload, lz4 — быстрый |
Практическая зависимость: сжатие работает на батч — без батча (linger=0, батч=1 сообщение) компрессор сжимает воздух. Порядок включения: сначала батчинг, потом сжатие, потом только смотреть на латентность.
Ориентиры (замерьте свои — модуль 3 даёт методику):
| Профиль | linger/batch | compression | Типичный эффект |
|---|---|---|---|
| 低-latency команды | 0–1 мс | none | p99 < 10 мс, throughput умеренный |
| Событийный лог | 5–20 мс | lz4 | p99 +5–20 мс, throughput ×3–5 |
| Bulk-архив | 50–100 мс | zstd | p99 десятки мс, сеть ÷4–8 |
📖 4.2. Тюнинг консьюмера: fetch и параллелизм
fetch.min.bytes/fetch.max.wait.ms— брокер копит ответ: больше минимальных байт = меньше пустых fetch (но +latency);MaxPollRecords(kafka-go:MaxRecords?) — сколько за один poll; связка сmax.poll.interval.ms— Модуль 3 (rebalance storm);- Партиции — потолок параллелизма группы: консьюмеров больше партиций — лишние простаивают (Модуль 1, quiz). Планируйте партиции под пиковое число консьюмеров заранее: их число можно только увеличивать, и новые партиции получают только новые ключи;
- Static membership (
group.instance.id, kafka-go:InstanceID) — рестарт pod не уводит группу в rebalance: лобби ждёт вернувшегося членаsession.timeout. Обязательно для Kubernetes-деплоя с рестартами.
📖 4.3. Exactly-once в проде: где транзакции нужны, где — самообман
Честная таблица (Модуль 1 дал механику — здесь эксплуатация):
| Сценарий | Нужны транзакции Kafka? |
|---|---|
| Kafka → Kafka (пайплайн внутри) | Да: consume-transform-produce атомарно |
| Kafka → БД | Нет: транзакции не покрывают БД; атомарность даёт Outbox (Фундамент, М2) + идемпотентный sink |
| Kafka → внешний мир | Нет: идемпотентность + ретраи |
| Счётчики/агрегаты в стриме | Часто достаточно идемпотентности |
Цена транзакций: +латентность (commit между батчами), throughput ниже,
transactional.id — по одному на инстанс (иначе fencing убьёт живого),
read_committed у консьюмеров, LSO-мониторинг. Ориентир: tens-of-percent
по латентности и заметная инженерная сложность.
В Go честно: segmentio/kafka-go транзакций не поддерживает.
Для EOS в Go — twmb/franz-go (полная поддержка транзакций) или
confluent-kafka-go. Решение «нужны транзакции» в Go-проекте — это
решение «меняем клиентскую библиотеку», закладывайте заранее.
Разворачивание (checklist):
transactional.id={service}-{pod}(уникален и стабилен);- продюсер:
BeginTransaction → produce → Commit; при ошибке — Abort и повтор батча; - консьюмер:
read_committed; в обработчике — commit офсетов ПОСЛЕ commit транзакции выходных данных; transaction.timeout.ms> max время батча; мониторинг aborted-транзакций.
📖 4.4. Multi-DC: MirrorMaker 2 и границы
Между дата-центрами Kafka не умеет «сам» — есть MirrorMaker 2 (Kafka↔Kafka репликация топиков/офсетов, асинхронная):
- Active-Passive: один ДЦ пишет, MM2 догоняет стендайби; RPO =
задержка репликации (секунды); failover ручной, офсеты конвертируются
(
remote.topicпрефиксы); - Active-Active: два кластера, двусторонняя реплика с prefix — циклы режутся, но дубли/порядок между ДЦ не гарантированы;
- Stretch cluster (брокеры в двух ДЦ как один кластер) — только при стабильной сети между ДЦ с малой задержкой.
Правило: cross-DC — это всегда асинхронная репликация с потерей хвоста при аварии; строгие гарантии между ДЦ не даёт ни один брокер (сравните с §4.4 курса Redis — same problem, same conclusion).
💻 Практика
- Sweep linger/батча: продюсер 5 000 msg/s payload 1 КБ; сетка linger 0/5/20 мс — замерьте p99 латентность и messages-in на графике (Модуль 2). Таблица «latency vs throughput» — ваш главный артефакт.
- Сжатие: та же нагрузка с none/lz4/zstd — замерьте bytes-in (экономия сети) и CPU продюсера. Для payload 1 КБ JSON ожидание: lz4 ≈ ÷3–4 по сети, zstd ≈ ÷5–6 при большем CPU.
- Static membership: два консьюмера с
InstanceID; рестарт одного — убедитесь по логам/метрикам, что rebalance НЕ происходит (партиции ждут члена), и возвращение не меняет раскладку. - EOS-скетч (по желанию): возьмите
franz-go, повторите лабораторию «Event-sourced счёт» (Модуль 1) с транзакциями иread_committed-консьюмером; уроните продюсер посреди транзакции — консьюмер не видит «полубатч».
Артефакт: таблица замеров linger/compression + конфигурация продюсера/консьюмера для вашего профиля + решение по EOS (ADR-строка).
🧩 Quiz "Проверь себя"
✅ Итоги модуля
| Тема | Главное |
|---|---|
| Продюсер | Батчинг → сжатие → латентность; профили «команды/лог/архив» |
| Консьюмер | Партиции = потолок параллелизма; static membership для K8s |
| EOS | Kafka→Kafka — да (franz-go в Go); через БД — Outbox + идемпотентность |
| Multi-DC | MM2, асинхронно, потеря хвоста при аварии — как у всех брокеров |
| Методика | Замерять linger/compression на своём payload — цифры вместо мнений |
🎓 Куда дальше
Курс Kafka закрыт полностью: модель (М1) → мониторинг (М2) → инциденты (М3) → тюнинг и EOS (М4). В треке остаются «RabbitMQ» (модель → мониторинг → инциденты → Streams/multi-DC) и капстоун «OrderFlow» — там эти навыки собираются в один пайплайн из трёх брокеров.