Питання 2 · Розділ 15

Що таке партиція (partition) і навіщо вона потрібна

Уявіть поштове відділення з кількома вікнами:

Мовні версії: English Russian Ukrainian

🟢 Junior Level

Що таке партиція?

Партиція (Partition) — це «під-канал» всередині топiка. Кожна партиція — це впорядкована, незмінна послідовність повідомлень, яка постійно доповнюється.

Навіщо партиції: механізм паралелізму. Одна партиція = один консюмер може її читати. Якби топік був одним цілим, тільки один консюмер міг би працювати. Партиції дозволяють N консюмерам читати один топік одночасно.

Аналогія

Уявіть поштове відділення з кількома вікнами:

  • Топiк — це все поштове відділення
  • Партиції — це окремі вікна обслуговування
  • Кожне вікно обслуговує свою чергу клієнтів незалежно
  • Клієнти в одній черзі обслуговуються строго по порядку
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. Відмовостійкість — кожна партиція реплікується на кілька брокерів

Простий приклад

# Створення топiка з 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 (непарна кількість, мін. 3 вузли):
  - Зберігає метадані у __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. Плануйте партиції заздалегідь — їх не можна зменшити без перестворення топiка
  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. Що таке топiк (topic) у Kafka]]
  • [[3. Як дані розподіляються по партиціях]]
  • [[17. Що таке leader та follower репліки]]
  • [[18. Що таке ISR (In-Sync Replicas)]]
  • [[28. Як видаляються старі повідомлення з топiка]]