Транспорт бэкенда
Apache Kafka: event streaming платформа
LinkedIn в 2010 году имел сотни сервисов и десятки источников данных - и интеграция превратилась в граф point-to-point ETL-джоб, который никто не мог удержать в голове. Команда Джея Крепса посмотрела на проблему иначе: что если данные движутся не очередями между сервисами, а единым append-only логом, который служит источником правды? Так родилась Kafka. К 2025 году она стала фундаментом стриминговой обработки в Netflix, Uber, Goldman Sachs, Pinterest, Airbnb - везде, где события важны как непрерывный поток, а не как разрозненные сообщения. Понимание партиций, оффсетов и exactly-once - ключ к проектированию современных event-driven систем.
- **Netflix:** Kafka обрабатывает 7 триллионов событий в день - просмотры, рекомендации, биллинг - всё течёт через топики
- **Uber:** event bus на Kafka координирует диспатчеризацию поездок и обновления карт в реальном времени; задержки измеряются миллисекундами
- **Goldman Sachs / JPMorgan:** транзакционные пайплайны на Kafka EOS обеспечивают точный учёт и аудит финансовых событий
Топики и партиции: фундамент Kafka
В 2010 году LinkedIn упёрся в проблему: десятки сервисов писали логи и события в десятки приёмников, граф интеграций превратился в спагетти. Команда во главе с Джеем Крепсом перепридумала очередь как распределённый append-only лог - так появилась Kafka. Топик в Kafka - это не очередь сообщений, а лог. Каждый топик разрезается на N партиций - физических лог-файлов на дисках брокеров. Сообщения внутри партиции упорядочены и иммутабельны. Между партициями порядок не гарантируется. Эта модель отличает Kafka от RabbitMQ: там очередь - point-to-point, здесь лог - shared substrate.
Партиция - единица параллелизма и репликации. Топик с N партициями может обрабатываться N консьюмерами параллельно. Каждая партиция реплицируется на M брокеров (replication factor). Лидер партиции принимает запись, фолловеры догоняют. Сообщение в партицию выбирается по hash(key) % N или round-robin при отсутствии ключа. Ключ - механизм партиционирования, который сохраняет порядок: все события одного пользователя попадают в одну партицию и обрабатываются последовательно.
Топик содержит 12 партиций. Сообщения отправляются без ключа round-robin. Какой порядок гарантирует Kafka?
Продюсеры и консьюмеры: запись и чтение лога
Продюсер - клиент, отправляющий сообщения в топик. Он сам выбирает партицию через partitioner (по ключу или кастомной логикой) и шлёт запись на лидера этой партиции. Параметр acks определяет надёжность: 0 - fire-and-forget, 1 - подтверждение от лидера, all - подтверждение всех ISR-реплик. Консьюмер - клиент, читающий сообщения из топика. Он pull-based: сам решает, когда и сколько забрать. Это отличает Kafka от push-моделей вроде классических брокеров - бэкпрессура встроена в саму архитектуру.
Idempotent producer (enable.idempotence=true) присваивает каждой записи producer ID + sequence number. Брокер дедуплицирует повторные отправки в пределах одной сессии - устраняет дубли при ретраях. Это базовая гарантия: ровно одна запись на партицию при сетевых таймаутах. Batching: продюсер копит сообщения в буфере linger.ms миллисекунд и шлёт пачкой - амортизирует сетевые издержки. На production-нагрузке один продюсер выдаёт 100K+ msg/sec.
Зачем продюсер использует enable.idempotence=true вместе с acks=all?
Consumer Groups: горизонтальное масштабирование чтения
Consumer Group - механизм, объединяющий N экземпляров одного сервиса для параллельной обработки топика. Брокер автоматически распределяет партиции между членами группы: каждая партиция назначается ровно одному консьюмеру внутри группы. Это даёт точную единицу масштабирования: если в топике 12 партиций, группа из 12 консьюмеров обрабатывает их параллельно. Добавили 13-го - он простаивает: партиций не хватило. Отвалился один - брокер запускает rebalance и переназначает партиции живым членам.
Группа идентифицируется group.id. Брокер хранит для каждой группы committed offset по каждой партиции в внутреннем топике __consumer_offsets. При перезапуске консьюмер продолжает с сохранённого оффсета. Несколько разных групп читают один топик независимо: классический паттерн pub/sub - один топик 'transactions' читают группа billing, группа analytics, группа fraud-detector. Каждая ведёт свой оффсет и обрабатывает все сообщения.
Топик имеет 8 партиций. Сервис billing работает в Consumer Group из 12 инстансов. Что произойдёт?
Оффсеты и retention: воспроизводимое прошлое
Оффсет - монотонно растущий номер позиции в партиции. Kafka не удаляет сообщение после доставки - оно лежит в логе, пока действует retention. Retention управляется двумя параметрами: log.retention.hours (по времени) и log.retention.bytes (по размеру партиции). При срабатывании любого срез старых сегментов удаляется целиком. Это значит, что любой потребитель может перечитать прошлое - реплеить события для бэкфилла, миграции схемы или восстановления downstream-системы. Это ключевое отличие от очередей: лог - не транзитное хранилище, а долговременная история.
Log compaction - альтернативный режим хранения, при котором Kafka гарантирует сохранение последней записи по каждому ключу. Старые версии того же ключа удаляются фоновым процессом. Это превращает топик в материализованное представление: 'компактный топик users' хранит актуальное состояние каждого пользователя. Стриминговые системы (Kafka Streams, Flink, ksqlDB) используют это для построения tables над логами - основа Change Data Capture (CDC) пайплайнов.
Bug в обработчике аналитики прожил в проде неделю. Какое свойство Kafka позволяет починить и пересчитать данные без участия продюсеров?
Exactly-Once Semantics: транзакции и идемпотентность
По умолчанию Kafka даёт at-least-once: сообщение может быть доставлено повторно при сетевых ошибках. Exactly-Once Semantics (EOS), введённая в Kafka 0.11 - это комбинация идемпотентного продюсера и транзакционного API, которая гарантирует, что результат обработки виден ровно один раз. Транзакция Kafka объединяет в атомарную единицу: чтение из топика + бизнес-обработку + запись результата в другой топик + коммит оффсета. Либо всё применяется, либо ничего. Это фундамент стриминговых процессоров: Kafka Streams и ksqlDB используют EOS под капотом.
Транзакция стартует с initTransactions(), внутри происходят send() и sendOffsetsToTransaction(), завершается commitTransaction() или abortTransaction(). Брокер хранит состояние транзакции в специальном топике __transaction_state. Консьюмеры с isolation.level=read_committed видят только закоммиченные транзакции - незакоммиченные сообщения остаются невидимыми. Цена EOS - задержка коммита и дополнительные round-trip до брокера; пропускная способность падает на 10-30%.
Kafka - это просто 'быстрый RabbitMQ' для микросервисов
Kafka - распределённый коммит-лог: топик хранится долго, читается произвольным числом независимых консьюмеров и поддерживает реплей. RabbitMQ - брокер очередей: сообщение доставляется и удаляется
Из модели лога вытекает всё ключевое: реплей старых событий для бэкфилла, материализованные представления через log compaction, source-of-truth для CDC и event sourcing, exactly-once семантика через транзакции. Использование Kafka как 'просто очереди' оставляет 80% мощности неиспользованной
Что отличает Exactly-Once Semantics Kafka от обычного at-least-once с идемпотентным потребителем?
Ключевые идеи
- **Топик - распределённый лог из партиций.** Партиция - единица параллелизма, репликации и порядка; глобального порядка между партициями Kafka не даёт.
- **Продюсер выбирает партицию по ключу,** idempotent + acks=all гарантируют ровно одну запись в партицию при сбоях и таймаутах.
- **Consumer Group масштабирует чтение:** партиция отдаётся ровно одному консьюмеру внутри группы; разные группы читают тот же топик независимо.
- **Оффсеты + retention делают прошлое воспроизводимым:** реплей через --reset-offsets, log compaction строит материализованное представление по ключу.
- **Exactly-Once Semantics через транзакции:** read-process-write становится атомарным; основа Kafka Streams и стриминговых ETL.
Связанные темы
Kafka вырастает из идей очередей сообщений и распределённых систем, но переосмысляет их вокруг лога.
- RabbitMQ и AMQP — Сравнение моделей: RabbitMQ - очередь с уничтожением сообщения, Kafka - лог с retention и реплеем
- Распределённые системы: репликация — Партиции реплицируются по принципу leader-follower с ISR, тем же, что Raft/MultiPaxos для согласованности
Вопросы для размышления
- У LinkedIn граф point-to-point интеграций оказался непосильным. Где проходит граница, после которой имеет смысл переходить с прямых очередей на единый event bus вроде Kafka?
- Exactly-Once в Kafka стоит 10-30% пропускной способности. Какие классы задач оправдывают эту цену, а где достаточно at-least-once + идемпотентного потребителя?
- Log compaction превращает топик в материализованное представление по ключу. Где это удобнее, чем держать ту же таблицу в Postgres и публиковать события об изменениях?
Связанные уроки
- bt-11-messaging-intro — Messaging patterns - фундамент перед Kafka
- bt-12-rabbitmq — RabbitMQ - альтернатива с push-моделью
- bt-14-kafka-deep — Internals ISR и compaction - следующий уровень
- bt-17-event-driven — Kafka - backbone event-driven архитектуры
- ds-03-consensus — ISR replication использует Zookeeper/KRaft консенсус
- bd-01 — Kafka - стандартный ingestion layer для big data
- dist-07-transactions