Kafka это распределённый журнал: продюсеры пишут сообщения в топики, консьюмеры читают по смещению (offset), сообщения хранятся заданное время независимо от прочтения. Топик делится на партиции: партиция это упорядоченный журнал на одном брокере (с репликами), единица параллелизма и хранения. Порядок FIFO гарантируется только внутри партиции; на уровне топика с несколькими партициями порядка нет. Консьюмеры в группе делят партиции между собой, одна партиция читается одним консьюмером группы.
При наличии ключа партиция вычисляется как хеш ключа по модулю числа партиций: все сообщения с одним ключом (клиент, заказ) попадают в одну партицию и читаются по порядку, что нужно для событий одной сущности. Без ключа продюсер распределяет сообщения сам: Round Robin по кругу (по одному в каждую партицию поочерёдно) или, в новых версиях, sticky-партиционирование батчами. Простой самодельный способ без Round Robin: случайная партиция или счётчик по модулю числа партиций, оба дают равномерность в среднем; важно не брать поле с перекосом (один крупный клиент) как ключ, иначе одна партиция перегружена.
Схема: CDC из базы через Debezium в Kafka (топик на таблицу, ключ = первичный ключ), консьюмер (Spark Structured Streaming, Kafka Connect sink, Flink или свой сервис) пишет батчами в хранилище с дедупликацией по ключу и смещению. Гарантия «хотя бы раз» делает идемпотентную запись обязательной: upsert или ReplacingMergeTree. Контролировать лаг консьюмера, схему сообщений (Schema Registry, Avro) и обработку удалений (tombstone-сообщения).
Брокеры хранят данные и обслуживают клиентов, кластер из нескольких брокеров координируется через KRaft (раньше ZooKeeper). Продюсеры пишут сообщения в топики, консьюмеры читают. Топик логическое имя потока, разбитое на партиции: упорядоченные журналы, размазанные по брокерам, с репликами. Группа консьюмеров делит партиции топика между своими участниками, чтобы читать параллельно и каждое сообщение обрабатывать один раз на группу. Смещения консьюмеров хранятся во внутреннем топике __consumer_offsets. Вокруг ядра: Kafka Connect для интеграций, Schema Registry для схем, Streams и ksqlDB для обработки.
Offset это порядковый номер сообщения внутри партиции, монотонно растущий. Консьюмер запоминает, до какого смещения он дочитал, и фиксирует его (commit) во внутренний топик __consumer_offsets с ключом «группа, топик, партиция». Хранение сообщений от чтения не зависит: они живут до истечения retention, поэтому группу можно сбросить на начало или на конкретное время (--reset-offsets) и перечитать историю, а новая группа читает с конца или с начала по настройке auto.offset.reset. Коммитить можно автоматически по таймеру или вручную после успешной обработки, от этого зависят гарантии доставки.
Фактор репликации это число копий каждой партиции на разных брокерах. Одна из реплик лидер: только она принимает запись и чтение, остальные фолловеры копируют её журнал. ISR (in-sync replicas) это реплики, которые не отстают от лидера больше допустимого; при acks=all запись считается подтверждённой, когда её получили все реплики из ISR. Если брокер падает, контроллер выбирает новым лидером одну из реплик ISR, клиенты переключаются сами и работа продолжается без потери данных. Под-реплицированные партиции (under-replicated) один из главных показателей здоровья кластера.
Партиция читается ровно одним консьюмером группы, поэтому число партиций это максимальный параллелизм чтения. Консьюмеров больше, чем партиций: лишние простаивают, но подхватят партиции при выбытии других (горячий резерв). Консьюмеров меньше: кто-то читает несколько партиций и может не успевать. Два консьюмера в разных группах читают все сообщения каждый, это нужно, когда один поток обрабатывают разные сервисы.
At most once: коммит смещения до обработки, при падении сообщение теряется. At least once: коммит после обработки, при падении между ними сообщение придёт снова, нужны идемпотентные потребители. Exactly once складывается из трёх частей: идемпотентный продюсер (enable.idempotence=true, брокер отбрасывает повтор по номеру последовательности при ретраях), транзакции продюсера, которые атомарно пишут в несколько партиций и фиксируют смещения потребителя (isolation.level=read_committed у читателя), и идемпотентная запись в конечное хранилище, потому что гарантия Kafka заканчивается на её границе: в базу или в S3 данные всё равно нужно писать с upsert или по ключу.
acks=0: продюсер не ждёт подтверждения, максимальная скорость и возможная потеря. acks=1: подтверждает лидер после записи в свой журнал; если лидер упадёт до репликации, сообщение потеряется. acks=all (-1): ждут все реплики из ISR, вместе с min.insync.replicas=2 это защита от потери при отказе одного брокера. Производительность продюсера дальше регулируют батчингом (batch.size, linger.ms: накопить пачку, прежде чем отправить) и сжатием пачек (lz4, snappy, zstd, gzip), которое экономит сеть и диск брокеров.
Перераспределение партиций между участниками группы: происходит, когда консьюмер добавился, упал, не успел прислать heartbeat или слишком долго обрабатывал пачку (max.poll.interval.ms). Во время классического ребаланса вся группа останавливает чтение, растёт лаг, а незакоммиченные сообщения приходят повторно. Лечат кооперативным протоколом (CooperativeStickyAssignor), статическим членством (group.instance.id), адекватным размером пачки и таймаутами; «штормы ребалансов» обычно означают, что обработка одной пачки дольше лимита.
Лаг это разница между последним смещением в партиции и последним закоммиченным смещением группы, то есть сколько сообщений ещё не обработано. Растущий лаг означает, что потребители не справляются (backpressure) или остановились. Помимо лага смотрят пропускную способность по топикам, число under-replicated партиций, состояние ISR, место на дисках брокеров и частоту ребалансов. Инструменты: JMX-метрики через Kafka Exporter в Prometheus и Grafana, Burrow для лага, панели Kafka UI или Conduktor.
Retention удаляет сегменты журнала по времени или размеру независимо от того, прочитаны ли они: так хранят потоки событий. Compaction (cleanup.policy=compact) оставляет для каждого ключа только последнее значение, старые версии вычищаются в фоне: так хранят состояние, справочники, CDC-снимки, и такой топик можно перечитать с начала, чтобы восстановить текущее состояние. Удаление ключа делают tombstone-сообщением с null в значении: после компакции и тайм-аута ключ исчезает. Политики можно совмещать.
Kafka это распределённый append-only журнал: сообщения не удаляются при чтении, у каждого потребителя своё смещение, историю можно перечитать, порядок гарантирован внутри партиции. RabbitMQ классический брокер очередей: сообщение доставляется потребителю и удаляется, есть богатая маршрутизация (exchanges, routing keys), подтверждения и приоритеты. Kafka выбирают для высокой пропускной способности, стриминга событий, CDC и интеграции многих потребителей с replay; RabbitMQ для задач и команд между сервисами с низкой задержкой и сложной маршрутизацией.
Больше партиций: выше параллелизм чтения и записи, можно масштабировать группу консьюмеров. Но каждая партиция это открытые файлы и память на брокере, более долгий ребаланс и восстановление лидера, больше мелких батчей у продюсера, а порядок всё равно только внутри партиции. Число партиций увеличить можно, уменьшить нельзя, и при увеличении ключи переезжают в другие партиции, ломая локальность. Практика: считать от целевой пропускной способности и числа потребителей с запасом в два-три раза, обычно десятки, а не тысячи.
Последовательная запись и чтение журнала на диске без случайного доступа, батчинг и сжатие пачек на стороне продюсера, zero-copy передача сегментов из page cache в сокет без копирования в память процесса, опора на page cache ОС вместо собственного кэша, отсутствие состояния потребителей на брокере (только смещения) и горизонтальное масштабирование партициями.
Растущий лаг, когда потребители не успевают; штормы ребалансов из-за долгой обработки пачек; дубли при at-least-once без идемпотентной записи; перекос партиций из-за плохого ключа, когда одна партиция перегружена; under-replicated партиции при проблемах с брокером или сетью; переполнение дисков при неверно подобранном retention; несовместимые изменения схемы сообщений, если нет Schema Registry; потеря сообщений при acks=1 или min.insync.replicas=1.
Фреймворк для переноса данных между Kafka и внешними системами без своего кода: source-коннекторы читают из источников (Debezium из баз, файлы, S3) в топики, sink-коннекторы пишут из топиков в приёмники (ClickHouse, Postgres, S3, Elasticsearch). Конфигурация в JSON, масштабирование воркерами, встроенные преобразования (SMT), управление смещениями. Для DE это стандартный способ поднять CDC и доставку в хранилище, оставив код только для нестандартной логики.
Отдельный сервис, который хранит схемы сообщений (Avro, Protobuf, JSON Schema) по версиям и выдаёт им идентификаторы. Продюсер сериализует сообщение по схеме и кладёт в заголовок только её id, потребитель по id получает схему и разбирает данные. Главное свойство: проверка совместимости при регистрации новой версии (backward, forward, full), которая не даёт выкатить изменение, ломающее потребителей, например удалить обязательное поле. Для Data Engineer это ещё и источник схем для автоматического создания таблиц в хранилище.
DLQ это отдельный топик, куда потребитель или коннектор отправляет сообщения, которые не удалось обработать (битый формат, нарушение схемы, ошибка записи), чтобы не останавливать поток и не терять данные; их потом разбирают и переигрывают. Backpressure это ситуация, когда потребитель не успевает за продюсером: в Kafka она проявляется растущим лагом, поскольку брокер данные буферизует, а не отбрасывает. Снимают её масштабированием группы (до числа партиций), батчевой обработкой, оптимизацией записи в приёмник или замедлением продюсера.
Сервисы общаются не прямыми вызовами, а событиями «что произошло»: заказ создан, платёж прошёл. Продюсер не знает потребителей, потребителей может быть много и они добавляются без изменений источника. Kafka выступает шиной событий: хранит их историю, даёт replay новым потребителям и развязывает скорость производителя и потребителей. Для DE это означает, что хранилище подписывается на те же события, что и сервисы, вместо ночных выгрузок из баз.