Вопрос 2 · Раздел 15

Что такое партиция (partition) и зачем она нужна

Представьте почтовое отделение с несколькими окнами:

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

Что такое партиция?

Партиция (Partition) — это «под-канал» внутри топика. Каждая партиция — это упорядоченная, неизменяемая последовательность сообщений, которая постоянно дополняется.

Зачем партиции: механизм параллелизма. Одна партиция = один консьюмер может её читать. Если бы топик был одним целым, только один консьюмер мог бы работать. Партиции позволяют N консьюмерам читать один топик одновременно.

Аналогия

Представьте почтовое отделение с несколькими окнами:

  • Топик — это всё почтовое отделение
  • Партиции — это отдельные окна обслуживания
  • Каждое окно обслуживает свою очередь клиентов независимо
  • Клиенты в одной очереди обслуживаются строго по порядку
Topic "orders"
┌─────────────┬─────────────┬─────────────┐
│Partition 0  │Partition 1  │Partition 2  │
│ msg-0       │ msg-0       │ msg-0       │
│ msg-1       │ msg-1       │ msg-1       │
│ msg-2       │ msg-2       │ msg-2       │
└─────────────┴─────────────┴─────────────┘

Зачем нужны партиции?

  1. Параллелизм — каждая партиция читается одним консьюмером. 10 партиций = 10 консьюмеров работают параллельно
  2. Масштабирование — партиции распределяются по разным брокерам
  3. Отказоустойчивость — каждая партиция реплицируется на несколько брокеров

Простой пример

# Создание топика с 3 партициями
kafka-topics.sh --create \
  --topic orders \
  --partitions 3 \
  --replication-factor 3 \
  --bootstrap-server localhost:9092
3 партиции, 3 консьюмера:
  Consumer 1 → читает Partition 0
  Consumer 2 → читает Partition 1
  Consumer 3 → читает Partition 2

🟡 Middle Level

Анатомия партиции

Offset — порядковый номер каждого сообщения внутри партиции. Уникален только в рамках одной партиции.

Partition 0:
  offset=0  {"userId": 1, "action": "login"}
  offset=1  {"userId": 2, "action": "purchase"}
  offset=2  {"userId": 1, "action": "logout"}

Ordering — строгий порядок только внутри одной партиции. Глобального порядка в топике не существует.

Immutability — сообщения нельзя изменить или удалить выборочно. Удаление происходит целыми сегментами.

Репликация партиций

Partition 0 на кластере из 3 брокеров:
  Broker A → Leader (принимает read/write)
  Broker B → Follower (копирует с Leader)
  Broker C → Follower (копирует с Leader)
  • Чтение и запись всегда идут через лидера
  • Фолловеры пассивно копируют данные
  • При падении лидера один из фолловеров становится новым лидером

ISR (In-Sync Replicas)

ISR = множество реплик, которые «догнали» лидера

Leader: offset=100
Follower B: offset=100  → в ISR
Follower C: offset=95   → НЕ в ISR (отстаёт)

ISR = {Leader, Follower B}

Выбор количества партиций

Фактор Рекомендация
Throughput продюсера partitions >= producer_throughput / per_partition_throughput
Throughput консьюмера partitions >= consumer_throughput / per_consumer_throughput
Запас на рост Умножайте на 2-3

Как данные распределяются по партициям

partition = hash(key) % numPartitions    // если есть ключ
partition = round-robin / sticky         // если ключа нет

Типичные ошибки

Ошибка Последствие Решение
Одна партиция Нет параллелизма Минимум 3-6 партиций
Слишком много партиций Нагрузка на контроллер, память, FD Рекомендуется не более 2000-4000 на брокер для стабильной работы. Технический предел — ~200 000 (KRaft), но производительность контроллера деградирует.
Добавление партиций «на лету» Нарушение порядка ключей Планируйте заранее
Неравномерное распределение Hot partition Проверяйте распределение ключей

Добавление партиций

kafka-topics.sh --alter --topic orders --partitions 10

Последствия:

  1. Старые данные не перераспределяются — остаются в старых партициях
  2. Новые сообщения попадают во все партиции
  3. hash(key) % N меняется → те же ключи попадают в другие партиции → порядок нарушается

🔴 Senior Level

Внутреннее устройство партиции

На уровне кода Kafka каждая партиция представлена объектом Partition в ReplicaManager. Физически партиция — это набор сегментных файлов на диске:

/var/kafka-logs/orders-0/
├── 00000000000000000000.log          # Segment 0, data
├── 00000000000000000000.index        # Segment 0, sparse index (offset → physical position)
├── 00000000000000000000.timeindex    # Segment 0, time-based index (timestamp → offset)
├── 00000000000000000000.txnindex     # Segment 0, transaction index
├── 00000000000000100000.log          # Segment 1 (rotated at segment.bytes)
├── 00000000000000100000.index
├── 00000000000000100000.timeindex
├── leader-epoch-checkpoint           # Для предотвращения data loss при смене лидера
└── __txn_index__.0                   # Transaction metadata (если включены транзакции)

Log Segments — детали

segment.bytes=1GB          # Ротация по размеру
segment.ms=7 дней           # Ротация по времени
segment.jitter.ms=0         # Случайная задержка для избежания thundering herd
index.interval.bytes=4096   # Частота записей в индекс

Sparse Index: Индекс не для каждого сообщения, а каждые index.interval.bytes байт. Это делает индекс компактным (~1 запись на 4KB данных). Поиск: бинарный поиск по индексу → линейное сканирование в сегменте.

Leader/Follower Replication Protocol

Replica Fetcher Thread (на каждом Follower):
  1. Send FetchRequest → Leader
  2. Leader возвращает batch с current leader epoch
  3. Follower записывает в свой лог
  4. Обновляет LEO (Log End Offset)
  5. Leader обновляет HW (High Watermark)

High Watermark (HW) = последний offset, подтверждённый всеми ISR
Messages с offset > HW — не видны консьюмерам

LEO (Log End Offset) — номер последнего записанного сообщения.
HW (High Watermark) — номер последнего сообщения, подтверждённого всеми репликами. Консьюмеры видят только сообщения до HW.

Leader Epoch: Введён в Kafka 0.11 для устранения data loss при смене лидера. Хранится в leader-epoch-checkpoint файле. При failover новый лидер обрезает лог до последнего подтверждённого epoch.

ISR (In-Sync Replicas) — глубинный механизм

Условия нахождения в ISR:
  replica.lag.time.max.ms=30000  // Follower должен «догонять» за 30 секунд
  replica.fetch.wait.max.ms=500  // Максимальное ожидание fetch request

Replica State Machine:
  NewReplica → OnlineReplica → Leader/Follower → OfflineReplica → NonExistentReplica

OSR (Out-of-Sync Replicas): Реплики, отставшие от лидера. Не учитываются при min.insync.replicas.

KRaft Mode (Kafka Raft Metadata)

До KRaft: ZooKeeper хранил метаданные кластера (топики, партиции, контроллер, ISR).

С KRaft (Kafka 3.3+ production-ready):

Controller Quorum (odd number, min 3 nodes):
  - Хранит метаданные в __cluster_metadata topic
  - Raft consensus protocol вместо ZK
  - Устраняет ZK как SPOF
  - Быстрее обработка изменений метаданных
  - Поддержка до 200 000 партиций (vs 200 000 с ZK с деградацией)

metadata.log.segment.bytes=100MB
metadata.log.segment.ms=1 день
metadata.max.retention.bytes=100MB

Edge Cases (3+)

  1. Unclean Leader Election: При unclean.leader.election.enable=true out-of-sync реплика может стать лидером → потеря данных (сообщения между HW старого лидера и LEO нового будут потеряны). Для финансовых систем всегда false.

  2. Split-Brain при KRaft: Если кворум контроллеров теряет большинство (например, 2 из 5 узлов упали), кластер не может менять метаданные (создавать топики, выбирать лидеров). Чтение/запись существующих партиций продолжают работать.

  3. Log Divergence при Network Partition: Если лидер и фолловер разделены сетью, лидер продолжает запись. При восстановлении связи фолловер усекается до HW лидера. Если HW ещё не обновился — возможна потеря подтверждённых продюсером сообщений (при acks=1).

  4. Metadata Propagation Delay: В больших кластерах (10K+ партиций) обновление метаданных (новый топик, новая партиция) может занимать 30-60 секунд. Продюсеры/консьюмеры получают stale metadata и отправляют сообщения в неправильные брокеры (NotLeaderForPartitionException).

  5. Disk Failure on Single Replica: Если диск с партицией вышел из строя, ISR уменьшается. Если min.insync.replicas не достигнут — продюсеры с acks=all получают NotEnoughReplicasException. Kafka не восстанавливает данные автоматически с других брокеров — требуется ручное вмешательство.

Performance Numbers

Метрика Значение Условия
Throughput на партицию ~50-100 MB/s SSD, batch.size=256KB, lz4
Max partitions/broker ~200 000 KRaft, 64GB RAM, SSD
Recommended partitions/broker 2 000-4 000 Для стабильной работы
Replication lag (нормальный) < 100 ms В пределах одного AZ
Leader election time 5-30 секунд Зависит от unclean.leader.election
Segment flush interval log.flush.interval.messages=9223372036854775807 (OS flush)  

Production War Story

Ситуация: E-commerce платформа с 50 партициями на топик orders. После добавления 50 партиций (итого 100) для масштабирования, клиенты начали жаловаться на «потерянные» заказы и дубликаты в обработке.

Корневая причина: При увеличении партиций hash(key) % 50 изменился на hash(key) % 100. События одного заказа попали в разные партиции. Сервис обработки заказов собирал события по ключу, но теперь «создание заказа» было в партиции 23, а «оплата» — в партиции 73. Обработчик платежа не нашёл исходный заказ.

Дополнительная проблема: 100 партиций × 3 реплики × 5 брокеров = 600 файловых дескрипторов только на один топик. Начались Too many open files ошибки.

Решение:

  1. Создали новый топик orders-v2 со 100 партициями
  2. Dual-write: продюсеры писали в оба топика 72 часа — продюсеры отправляли сообщения одновременно в старый и новый топик, 72 часа, чтобы консьюмеры могли дочитать старые данные из первого и начать читать новые из второго.
  3. Консьюмеры переключились на orders-v2
  4. Увеличили ulimit -n с 4096 до 65536
  5. Включили log.retention.check.interval.ms=300000 для своевременной очистки сегментов

Урок: Никогда не добавляйте партиции в топик, если используете ключи для гарантий порядка. Создавайте новый топик и мигрируйте трафик.

Мониторинг (JMX + Burrow)

JMX метрики партиций:

kafka.cluster:type=Partition,name=UnderReplicated
kafka.cluster:type=Partition,name=InSyncReplicasCount
kafka.server:type=ReplicaManager,name=IsrShrinksPerSec
kafka.server:type=ReplicaManager,name=IsrExpandsPerSec
kafka.log:type=Log,name=LogEndOffset,partition=0
kafka.log:type=Log,name=LogStartOffset,partition=0
kafka.log:type=Log,name=Size,partition=0

Burrow (consumer-centric):

  • Lag per partition — детализация до уровня партиции
  • Status: OK, WARN, ERR, STOP, STALL
  • HTTP API → Grafana → PagerDuty

Highload Best Practices

  1. Планируйте партиции заранее — их нельзя уменьшить без пересоздания топика
  2. Формула: partitions = max(prod_throughput, cons_throughput) / per_partition_capacity * 1.5
  3. Не более 4000 партиций на брокер — иначе деградация контроллера
  4. Используйте KRaft для новых кластеров — быстрее metadata propagation, нет ZK dependency
  5. Мониторьте ISR Shrink/Expand — индикатор проблем репликации
  6. Segment sizing: segment.bytes подбирайте так, чтобы сегмент закрывался за 1-4 часа
  7. OS tuning: vm.dirty_background_ratio=5, vm.dirty_ratio=10 для flush control
  8. Disk: SSD обязательны для production. NVMe для high-throughput (>50 MB/s на партицию)

🎯 Шпаргалка для интервью

Обязательно знать:

  • Партиция — механизм параллелизма: 1 партиция = 1 консьюмер
  • Порядок гарантирован строго внутри одной партиции (FIFO)
  • Offset — порядковый номер сообщения, уникален только в рамках партиции
  • Каждая партиция реплицируется: Leader (read/write) + Followers (copy)
  • ISR — реплики, синхронизированные с лидером; лидер выбирается только из ISR
  • hash(key) % N — при добавлении партиций распределение ключей меняется
  • Уменьшение партиций невозможно; добавление нарушает порядок по ключу
  • Рекомендуемый максимум: 2000-4000 партиций на брокер

Частые уточняющие вопросы:

  • Что будет при добавлении партиций? — Старые данные не перераспределяются; ключи попадут в другие партиции → порядок нарушен.
  • Что такое High Watermark? — Последний offset, подтверждённый всеми ISR. Консьюмеры видят только до HW.
  • Можно ли уменьшить партиции? — Нет, только удалить и создать топик заново.
  • Что такое Leader Epoch? — Номер поколения лидера; предотвращает data loss при смене лидера.

Красные флаги (НЕ говорить):

  • «Порядок гарантирован во всём топике» — только внутри партиции
  • «Можно уменьшить партиции» — невозможно
  • «Offset уникален глобально» — уникален только в партиции
  • «Добавление партиций безопасно при использовании ключей» — нарушает порядок

Связанные темы:

  • [[1. Что такое топик (topic) в Kafka]]
  • [[3. Как данные распределяются по партициям]]
  • [[17. Что такое leader и follower реплики]]
  • [[18. Что такое ISR (In-Sync Replicas)]]
  • [[28. Как удаляются старые сообщения из топика]]