Що таке партиція (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).