Что такое партиция (partition) и зачем она нужна
Каждая партиция представляет собой строго упорядоченную, неизменяемую последовательность сообщений (Append-Only Log), в которую новые события дописываются исключительно в конец,...
🟢 Junior Level
30-секундный ответ
Партиция (Partition) — это фундаментальная физическая единица масштабирования, хранения и параллелизма в Apache Kafka.
Каждая партиция представляет собой строго упорядоченную, неизменяемую последовательность сообщений (Append-Only Log), в которую новые события дописываются исключительно в конец, получая монотонно возрастающий порядковый номер — Offset.
Зачем нужны партиции:
- Параллелизм обработки: это ключевой закон Kafka: одну партицию в рамках одной Consumer Group может читать только ОДИН поток/инстанс консьюмера. Если у топика 10 партиций, вы можете запустить 10 параллельных инстансов микросервиса и ускорить чтение в 10 раз.
- Горизонтальное масштабирование: топик размером в 10 терабайт невозможно уместить на диск одного физического сервера. Партиции распределяются по разным серверам-брокерам в кластере.
- Отказоустойчивость: каждая партиция независимо реплицируется на другие брокеры (Leader $\to$ Followers).
Наглядная аналогия из жизни
Партиции — это многополосная платная автомагистраль с кассами оплаты:
- Топик — это вся трасса целиком.
- Партиции — это отдельные выделенные полосы движения (Полоса 0, Полоса 1, Полоса 2).
- Консьюмеры — это кассиры на пунктах взимания платы.
- Если у вас всего 1 полоса (1 партиция), то сколько бы кассиров (консьюмеров) вы ни наняли, на полосе сможет работать только один кассир, а за ним выстроится гигантская пробка. Сделав 6 полос, вы ставите 6 кассиров и пропускаете в 6 раз больше машин одновременно!
Базовый пример структуры топика
[ Топик "payments" (3 партиции) ]
├── [ Партиция 0 ]: [offset 0] ──► [offset 1] ──► [offset 2] ──► Consumer App Instance A
├── [ Партиция 1 ]: [offset 0] ──► [offset 1] ──► [offset 2] ──► Consumer App Instance B
└── [ Партиция 2 ]: [offset 0] ──► [offset 1] ──► [offset 2] ──► Consumer App Instance C
# Создание топика с явным указанием 6 партиций
kafka-topics.sh --bootstrap-server localhost:9092 \
--create \
--topic payments \
--partitions 6 \
--replication-factor 3
🟡 Middle Level
Анатомия партиции: Offset, LEO и High Watermark
Внутри каждой партиции существует чёткое разграничение состояний сообщений:
[ Физический лог Партиции 0 ]
Offset: 0 1 2 3 4 5 6 7
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│ msg │ msg │ msg │ msg │ msg │ msg │ msg │ msg │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
▲ ▲
│ │
[ High Watermark (HW) ] [ Log End Offset (LEO) ]
(Подтверждено ISR) (Записано на Лидере)
▲
│
Консьюмерам ДОСТУПНО только ДО HW!
- Offset: уникальный 64-битный порядковый номер сообщения внутри данной партиции. В партиции 0 есть offset=42, и в партиции 1 есть offset=42 — они не имеют друг к другу никакого отношения.
- Log End Offset (LEO): смещение следующего сообщения, которое будет записано в лог лидера партиции.
- High Watermark (HW): смещение последнего сообщения, которое было успешно скопировано всеми репликами из пула ISR (In-Sync Replicas).
- Критическое правило: консьюмеры никогда не увидят сообщения с offset между HW и LEO! Это гарантирует, что клиенты не прочитают «грязные» данные, которые могут быть потеряны в случае падения текущего лидера.
Как брокер распределяет данные по партициям
Когда продюсер отправляет ProducerRecord<Key, Value>:
- Если ключ указан (
key != null): брокер вычисляет хэш по алгоритму Murmur2: \(\text{partition} = \text{murmur2}(\text{key}) \pmod{\text{totalPartitions}}\) Все сообщения с одинаковым ключом (например,userId = "usr-889") гарантированно попадают в одну и ту же партицию, обеспечивая строгий порядок FIFO. - Если ключ отсутствует (
key == null): в современных версиях Kafka (с 2.4+) работает алгоритм Sticky Partitioner: продюсер пакует сообщения пачками (batches) в одну партицию до её заполнения, после чего переключается на следующую, многократно снижая задержки сети и повышая степень сжатия.
Правило сопоставления консьюмеров и партиций
В рамках одной группы потребителей (Consumer Group):
- Если
Partitions == Consumers(например, 4 партиции и 4 консьюмера) $\to$ идеальный баланс: каждому достается ровно по 1 партиции. - Если
Partitions > Consumers(например, 6 партиций и 2 консьюмера) $\to$ каждый консьюмер обрабатывает по 3 партиции. - Если
Consumers > Partitions(например, 3 партиции и 5 консьюмеров) $\to$ 2 консьюмера будут бессмысленно простаивать в холостом режиме (Idle), так как Kafka запрещает параллельное чтение одной партиции двумя инстансами одной группы!
🔴 Senior Level
Файловая структура сегментов на файловой системе Linux
На сервере брокера каждая партиция представляет собой обычную директорию в log.dirs (например, /var/lib/kafka/data/orders-0/):
/var/lib/kafka/data/orders-0/
├── 00000000000000000000.log # Фактические сжатые бинарные записи
├── 00000000000000000000.index # Разреженный индекс смещений: Offset -> File Position
├── 00000000000000000000.timeindex # Временной индекс: Timestamp -> Offset
├── leader-epoch-checkpoint # Контрольные точки смены лидеров (JEP/KIP-101)
└── partition.metadata # UUID партиции топика
Разреженный индекс (Sparse Index):
Kafka не индексирует каждое сообщение. Запись в .index вносится через каждые index.interval.bytes (по умолчанию 4096 байт / 4 КБ):
- Консьюмер запрашивает сообщение с
offset = 1530. - Брокер открывает файл
.index(отображённый в память черезmmap) и выполняет бинарный поиск ($O(\log N)$), находя ближайшую метку (например,offset 1500 = байт 61440). - Брокер переходит к смещению 61440 в файле
.logи производит короткое линейное сканирование всего 4 КБ данных до нужного смещения.
Защита от потери данных: Leader Epoch против High Watermark Truncation
До версии Kafka 0.11 репликация опиралась исключительно на High Watermark (HW). Это приводило к скрытым катастрофам при сбоях брокеров:
Сценарий сбоя без Leader Epoch:
- Реплика A (Лидер) и Реплика B (Follower) записали сообщение с
offset=1, но Реплика B упала до того, как обновила свой HW. - При перезагрузке Реплика B усекала свой лог до старого известного HW (удаляла
offset=1). - Если в этот момент Лидер A падал, Реплика B становилась новым лидером и записывала новое сообщение в
offset=1. - В результате лог расходился (Log Divergence), а подтверждённые данные безвозвратно терялись!
Решение — Leader Epoch (leader-epoch-checkpoint):
Каждая смена лидера инкрементирует монотонный номер эпохи (Leader Epoch, например Epoch 1, Epoch 2).
Вместо автоматического усечения лога по HW, вернувшийся из сбоя Follower опрашивает текущего Лидера: «Какое последнее смещение было записано в твоей эпохе?». Лидер отвечает точным значением, и фолловер усекает лог только до границы эпохи, исключая случайную потерю согласованных записей.
Проблема «Горячей партиции» (Hot Partition & Key Skew)
В продакшене часто возникает ситуация, когда один ключ генерирует 80% всех транзакций (например, в маркетплейсе ключ крупного продавца «Apple» или «Samsung» генерирует миллионы заказов, а мелкие ИП — по 5 заказов в день):
- Все сообщения с ключом «Apple» попадают строго в одну партицию (например, партицию 2).
- Брокер, обслуживающий партицию 2, упирается в 100% CPU и IOPS диска.
- Консьюмер, читающий партицию 2, накапливает огромный Consumer Lag, в то время как остальные 15 консьюмеров простаивают без работы!
Архитектурные решения проблемы Key Skew:
- Соление ключа (Key Salting): к популярному ключу добавляется случайный префикс или суффикс:
apple_0,apple_1,apple_2. Сообщения равномерно разлетаются по 3 партициям. - Compound Keys (Составные ключи): использование в качестве ключа связки
sellerId_orderIdвместо одногоsellerId.
4 Tricky Questions
1. В чём фундаментальная разница между Log End Offset (LEO) и High Watermark (HW), и почему консьюмер не может прочитать сообщения между ними?
Ответ:
- LEO (Log End Offset): смещение последнего сообщения, физически записанного на диске лидера партиции (включая сообщения, которые ещё не успели реплицироваться на фолловеров).
- HW (High Watermark): смещение последнего сообщения, которое было успешно скопировано и подтверждено ВСЕМИ репликами из синхронного пула ISR (In-Sync Replicas).
- Почему консьюмер не видит сообщения между HW и LEO: если бы консьюмер прочитал сообщение, находящееся выше HW (например, записанное на лидере, но ещё не отреплицированное на узел B), а лидер в этот момент физически сгорел, новым лидером стал бы узел B. На узле B этого сообщения нет, и оно будет заменено другим сообщением при следующей записи продюсера. Клиент столкнулся бы с иллюзией чтения «сообщения-призрака» (Dirty Read / Data Loss). Поэтому изоляция по High Watermark гарантирует согласованность чтения (Read Committed Isolation).
2. Почему наличие 50 000 партиций на одном брокере способно парализовать весь кластер Kafka, особенно в архитектурах с ZooKeeper?
Ответ: Большое количество партиций создаёт колоссальные накладные расходы по 4 направлениям:
- Дескрипторы файлов ОС (
ulimit -n): каждая партиция содержит минимум 3 открытых файла на сегмент (.log,.index,.timeindex). 50 000 партиций мгновенно съедают сотни тысяч дескрипторов файлов ОС. - Память ядра (OS Page Cache & JVM Metaspace): каждый индекс партиции удерживается в оперативной памяти через
mmap. Брокер исчерпывает виртуальную память. - Паузы при перевыборах лидера (Leader Election Storm): при падении брокера контроллер должен выбрать новых лидеров для 50 000 партиций. В кластерах с ZooKeeper это вызывало шквал ZK-запросов и заморозку кластера на несколько минут! (Проблема частично решена в KRaft с аппаратным батчингом метаданных, но лимит в 2000–4000 партиций на брокер остаётся лучшей практикой).
- Задержки репликации: потокам
ReplicaFetcherThreadприходится поочерёдно опрашивать тысячи очередей, что резко увеличивает задержки сквозной репликации (Replication Lag).
3. Что произойдет, если в топик с 4 партициями подключить Consumer Group из 6 инстансов микросервиса?
Ответ:
- 4 инстанса получат по 1 партиции и будут полноценно обрабатывать поступающие события.
- 2 оставшихся инстанса перейдут в состояние бездействия (Idle/Cold Standby) и не получат ни одного сообщения.
- Kafka фундаментально запрещает одновременное разделение одной партиции между несколькими консьюмерами одной группы, так как это мгновенно разрушило бы строгий порядок смещений (offset order).
- Однако эти 2 простаивающих пода служат «горячим резервом»: если один из работающих 4 инстансов упадет по OOM или выйдет из строя сервер, координатор группы инициирует Rebalance, и освободившаяся партиция моментально переназначится на один из свободных инстансов без необходимости ожидания запуска нового пода в Kubernetes.
4. Можно ли принудительно направить сообщение в конкретный номер партиции в обход стандартного Partitioner?
Ответ:
Да, можно.
В классе ProducerRecord в Java Kafka Client конструктор перегружен и принимает явный номер партиции:
// Явное указание целевой партиции: Integer partition = 2
ProducerRecord<String, String> record =
new ProducerRecord<>("orders", 2, System.currentTimeMillis(), "order-123", jsonPayload);
kafkaTemplate.send(record);
Если номер партиции передан явно (partition != null), стандартный DefaultPartitioner (хэширование ключа или sticky batching) полностью игнорируется, и сообщение отправляется строго в указанную партицию. Однако это считается жестким архитектурным антипаттерном (code smell), так как зашивает топологию кластера в исходный код бизнес-логики и приведёт к ошибкам IllegalArgumentException при переносе сервиса в окружение с другим числом партиций.
🎯 Шпаргалка для интервью
Главное о партициях
- Суть: фундаментальная единица параллелизма, физического шардирования и хранения в Kafka.
- Лог FIFO: неизменяемый append-only лог со строгим порядком смещений (Offset) внутри каждой партиции.
- Закон консьюмера: 1 партиция = максимум 1 консьюмер в рамках одной Consumer Group.
- High Watermark (HW): граница видимости данных консьюмерами; сообщения выше HW клиентам недоступны до подтверждения репликами.
- Leader Epoch: предотвращает потерю подтвержденных данных и расхождение логов при смене лидеров.
- Key Skew (Hot Partition): перекос нагрузки из-за одного популярного ключа; решается солением ключей (Salting).
- Разреженный индекс (.index): бинарный поиск по памяти
mmapкаждые 4 КБ лога. - Ограничения: уменьшить число партиций нельзя; рекомендуемый лимит — до 2 000–4 000 партиций на брокер.
Сводка метрик партиции
| Термин | Определение | Доступность для чтения |
| :— | :— | :— |
| Offset | Монотонный номер события в партиции | Да (если $\le$ HW) |
| LEO (Log End Offset) | Смещение следующей записи на Лидере | Нет (если > HW) |
| HW (High Watermark) | Смещение, подтверждённое всеми ISR | Граница чтения консьюмеров |
| ISR | Пул реплик, успевающих за лидером | Участвуют в формировании HW |
Красные флаги на собеседовании (Чего говорить нельзя)
- ❌ «Если консьюмеры не справляются, мы просто запустим 20 инстансов на топик из 4 партиций» — 16 инстансов будут простаивать в Idle.
- ❌ «Offset сообщения уникален во всем топике» — он уникален только в рамках конкретной партиции.
- ❌ «Консьюмер сразу читает любое сообщение, как только лидер записал его к себе на диск» — консьюмер видит только сообщения до High Watermark (HW).