Где размещать воркеры, читающие данные из Kafka и отправляющие в аналитику
Что проверяют
Умеете ли вы отделить потребителя очереди от остальной системы и подумать про масштаб, гарантии доставки и отказы. Ответ «запущу горутины в основном сервисе» здесь слабый. Следом спрашивают, как масштабировать воркеры и что будет с сообщением, если аналитика недоступна.
Как отвечать
Базовый выбор: отдельный деплоймент, а не горутина внутри API-сервиса. Причины:
- масштабирование: число консьюмеров определяется лагом, а не RPS пользователей;
- ребалансировки: каждый рестарт или автоскейлинг API перезапускал бы консьюмеров, и группа уходила бы в ребаланс;
- изоляция отказов: зависшая запись в аналитику не должна съедать память и соединения сервиса, который отвечает пользователям.
Потолок параллелизма задаёт число партиций: в одной consumer group партицию читает максимум один консьюмер. Внутри воркера обработку можно распараллелить горутинами, но оффсет тогда коммитят, только когда обработаны все сообщения до него.
ClickHouse и похожие хранилища любят пачки, поэтому воркер копит батч по размеру или таймеру, пишет его и только потом коммитит оффсеты. Это at-least-once: после падения пачка запишется повторно, значит нужна идемпотентность, например ReplacingMergeTree по ключу события. Битые сообщения уходят в DLQ-топик, чтобы не стопорить партицию.
Клиенты в Go: franz-go, segmentio/kafka-go, IBM/sarama, confluent-kafka-go (через cgo поверх librdkafka). Обязателен graceful shutdown по context: дописать батч и закоммитить до выхода. Главная метрика это consumer lag, по нему удобно масштабировать через KEDA.
Сильный ход: спросить, нужен ли свой воркер вообще. ClickHouse читает Kafka сам через движок Kafka, есть и готовые sink-коннекторы Kafka Connect.
Где спотыкаются
Ставят реплик больше, чем партиций, и ждут роста пропускной способности.
Коммитят оффсет сразу после чтения, до записи в хранилище, и теряют данные при падении.
Пишут в ClickHouse по строке на сообщение и упираются в слияние кусков.