Що таке партиція (partition) і навіщо вона потрібна
Уявіть поштове відділення з кількома вікнами:
🟢 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 │
└─────────────┴─────────────┴─────────────┘
Навіщо потрібні партиції?
- Паралелізм — кожна партиція читається одним консюмером. 10 партицій = 10 консюмерів працюють паралельно
- Масштабованість — партиції розподіляються по різних брокерах
- Відмовостійкість — кожна партиція реплікується на кілька брокерів
Простий приклад
# Створення топ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
Наслідки:
- Старі дані не перерозподіляються — залишаються у старих партиціях
- Нові повідомлення потрапляють у всі партиції
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+)
-
Unclean Leader Election: При
unclean.leader.election.enable=trueout-of-sync репліка може стати лідером → втрата даних (повідомлення між HW старого лідера та LEO нового будуть втрачені). Для фінансових систем завждиfalse. -
Split-Brain при KRaft: Якщо кворум контролерів втрачає більшість (наприклад, 2 з 5 вузлів впали), кластер не може змінювати метадані (створювати топіки, обирати лідерів). Читання/запис існуючих партицій продовжують працювати.
-
Log Divergence при Network Partition: Якщо лідер і фолловер розділені мережею, лідер продовжує запис. При відновленні зв’язку фолловер урізається до HW лідера. Якщо HW ще не оновився — можлива втрата підтверджених продюсером повідомлень (при
acks=1). -
Metadata Propagation Delay: У великих кластерах (10K+ партицій) оновлення метаданих (новий топік, нова партиція) може займати 30-60 секунд. Продюсери/консюмери отримують stale metadata і надсилають повідомлення неправильним брокерам (NotLeaderForPartitionException).
-
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.Рішення:
- Створили новий топік
orders-v2зі 100 партиціями- Dual-write: продюсери писали в обидва топіки 72 години — продюсери надсилали повідомлення одночасно в старий і новий топіки, 72 години, щоб консюмери могли дочитати старі дані з першого і почати читати нові з другого.
- Консюмери переключилися на
orders-v2- Збільшили
ulimit -nз 4096 до 65536- Увімкнули
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
- Плануйте партиції заздалегідь — їх не можна зменшити без перестворення топiка
- Формула:
partitions = max(prod_throughput, cons_throughput) / per_partition_capacity * 1.5 - Не більше 4000 партицій на брокер — інакше деградація контролера
- Використовуйте KRaft для нових кластерів — швидша metadata propagation, немає ZK dependency
- Моніторьте ISR Shrink/Expand — індикатор проблем реплікації
- Segment sizing:
segment.bytesпідбирайте так, щоб сегмент закривався за 1-4 години - OS tuning:
vm.dirty_background_ratio=5,vm.dirty_ratio=10для flush control - 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ка]]