Що таке топік (topic) в Kafka
Головна відмінність топіка Kafka від класичних черг повідомлень (RabbitMQ, ActiveMQ):
🟢 Junior Level
30-секундна відповідь
Топік (Topic) в Apache Kafka — це іменована логічна категорія (канал), що є розподіленим, незмінним журналом дозапису (Immutable Append-Only Commit Log), у який продюсери публікують події, а консьюмери їх вичитують.
Головна відмінність топіка Kafka від класичних черг повідомлень (RabbitMQ, ActiveMQ):
- У традиційній черзі: повідомлення видаляється брокером одразу після того, як один отримувач підтвердив його обробку (destructive read).
- У топіку Kafka: події НЕ видаляються під час читання. Вони зберігаються на диску протягом заданого періоду часу (retention policy, наприклад 7 днів). Безліч незалежних систем і мікросервісів можуть читати один і той самий топік паралельно, зі своєю власною швидкістю і навіть перечитувати старі дані заново з будь-якого моменту часу.
Наочна аналогія з життя
Топік — це щоденна друкована газета або журнал у бібліотеці:
- Видавництво (Продюсер) щодня друкує нові випуски та складає їх в архівну підшивку.
- Відвідувачі бібліотеки (Консьюмери) приходять до читальної зали. Один читає свіжий випуск за сьогодні, інший перечитує статті за минулий вівторок, а третій робить повну історичну виписку за весь рік.
- Читання газети одним читачем ніяк не знищує сторінки — журнал залишається лежати на полиці для інших людей, доки бібліотекар не спише старі випуски за регламентом зберігання (retention).
Базовий приклад відправлення та читання в Spring Boot 3
@Service
public class OrderEventService {
private final KafkaTemplate<String, String> kafkaTemplate;
public OrderEventService(KafkaTemplate<String, String> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
// Відправлення події в топік "orders" з ключем замовлення
public void publishOrderCreated(String orderId, String payload) {
kafkaTemplate.send("orders", orderId, payload);
}
// Незалежне читання з топіка групою мікросервісів
@KafkaListener(topics = "orders", groupId = "billing-service-group")
public void consumeForBilling(String message) {
// Обробка платежу
}
@KafkaListener(topics = "orders", groupId = "notifications-service-group")
public void consumeForNotifications(String message) {
// Відправлення SMS клієнту (читає той самий топік паралельно)
}
}
# Створення топіка через CLI: 3 партиції для паралелізму, фактор реплікації 3
kafka-topics.sh --bootstrap-server localhost:9092 \
--create \
--topic orders \
--partitions 3 \
--replication-factor 3
🟡 Middle Level
Логічна та фізична архітектура топіка
Топік — це лише логічна абстракція. Фізично на серверах-брокерах топік поділяється на партиції (Partitions):
[ Логічний Топік: orders ]
│
┌────────────────────────────────┼────────────────────────────────┐
▼ ▼ ▼
[ Партиція 0 ] [ Партиція 1 ] [ Партиція 2 ]
(Брокер 1, Лідер) (Брокер 2, Лідер) (Брокер 3, Лідер)
│ │ │
Файли на диску: Файли на диску: Файли на диску:
orders-0/ orders-1/ orders-2/
├── 00000.log (Дані) ├── 00000.log ├── 00000.log
├── 00000.index (Зсув/Offset) ├── 00000.index ├── 00000.index
└── 00000.timeindex (Час) └── 00000.timeindex └── 00000.timeindex
- Каталог на диску: на файловій системі брокера створюється каталог
<topic_name>-<partition_id>. - Сегменти журналу (
.log): дані записуються в активний файл сегмента. При досягненніsegment.bytes(за замовчуванням 1 ГБ) абоsegment.ms(за замовчуванням 7 днів) сегмент закривається (sealed) і створюється новий. - Індекси (
.index,.timeindex): розріджені індекси (Memory-Mapped Files), що дозволяють миттєво знаходити фізичне зміщення байтів на диску за порядковим номером (offset) або міткою часу події без повного сканування файлу.
Політики очищення журналу: delete vs compact
Поведінка видалення даних визначається конфігурацією cleanup.policy:
| Політика | Механізм | Призначення / Use Case |
|---|---|---|
delete (default) |
Старі сегменти безповоротно видаляються за часом (retention.ms) або за загальним розміром топіка (retention.bytes). |
Подієві потоки: аудит, метрики, клікстрім, замовлення. |
compact |
Фоновий процес LogCleaner видаляє застарілі дублікати записів з однаковим ключем, зберігаючи лише останній стан кожного ключа. |
Збереження агрегатів, профілі користувачів, кеш-таблиці (Kafka Streams KTable). |
Гарантії порядку повідомлень
- Всередині однієї партиції: гарантується суворий порядок FIFO (First-In, First-Out). Кожне повідомлення отримує монотонно зростаючий номер — Offset.
- У масштабі всього топіка (між різними партиціями): глобального порядку НЕ існує. Якщо в топіку 3 партиції, події будуть зчитуватися паралельно і можуть перемішуватися.
- Висновок: для забезпечення порядку пов’язаних бізнес-подій (наприклад, подій одного замовлення
order-123) продюсер зобов’язаний вказувати однаковий Message Key. Усі повідомлення з одним ключем гарантовано потраплять в одну й ту саму партицію.
🔴 Senior Level
Zero-Copy та операційна система: чому Kafka пише на диск швидше, ніж інші в пам’ять
Поширене запитання на співбесідах: «Як Kafka примудряється обробляти мільйони повідомлень на секунду, якщо вона кожне повідомлення пише на повільний диск?»
Секрет криється в архітектурі ядра Linux і відмові від кешування всередині JVM:
- Послідовне введення-виведення (Sequential I/O): Kafka лише дописує дані в кінець файлу (Append-Only). Швидкість послідовного запису на сучасний NVMe SSD сягає 3–5 ГБ/с, що порівнянно зі швидкістю оперативної пам’яті, виключаючи довільне позиціювання головки (Random I/O).
- Page Cache ядра Linux: брокер Kafka не зберігає повідомлення в Java Heap (що виключає накладні витрати на збирання сміття GC та оверхед службових заголовків об’єктів JVM). Усі записані дані залишаються в системному кеші сторінок ОС.
- Системний виклик
sendfile()(Zero-Copy Transfer): Під час передавання даних консьюмеру Kafka не копіює байти в користувацький простір Java. Ядро передає дані безпосередньо з кешу сторінок диска в мережевий сокет:Класичний підхід: Диск ──► Ядро (PageCache) ──► JVM Heap ──► Буфер сокета ──► NIC (4 копіювання) Zero-Copy (sendfile): Диск ──► Ядро (PageCache) ──────────────────────────────► Мережева карта (NIC DMA)
Log Compaction, Tombstones і ризик «Воскресіння даних»
При використанні cleanup.policy=compact:
- Якщо клієнт хоче видалити запис із ключем
userId=42, продюсер відправляє в топік спеціальний маркер видалення — Tombstone: повідомлення з ключемkey="42"і порожнім тіломvalue=null. - Фоновий потік
LogCleanerпід час проходу стискає сегменти, видаляє всі старі версії значення ключа42і тимчасово залишає сам маркер Tombstone. - Небезпечна помилка (Data Resurrection): параметр
delete.retention.ms(за замовчуванням 24 години) визначає, як довго Tombstone фізично зберігається на диску. Якщо повільний або аварійно зупинений консьюмер був вимкнений довше, ніжdelete.retention.ms, при повторному запуску він не побачить Tombstone і відновить старий стан сутності з попередніх резервних копій!
Контракт надійності: Тріада acks=all, min.insync.replicas та RF
Для запобігання втраті даних у критичних фінансових топіках використовується сувора зв’язка налаштувань:
replication.factor = 3(3 фізичні копії партиції на різних серверах).min.insync.replicas = 2(мінімум 2 живі репліки повинні підтвердити запис).acks = all(або-1) на стороні Producer.
Граничний сценарій (Edge Case): Якщо в кластері з 3 брокерів два вузли виходять з ладу, у строю залишається лише лідер.
- Продюсер з
acks=allнамагається надіслати повідомлення. - Лідер бачить, що розмір пулу синхронних реплік (ISR) впав до 1, що менше ніж
min.insync.replicas: 2. - Лідер відхиляє запис і викидає продюсеру
NotEnoughReplicasException. Це захищає систему від втрати даних під час подальшого перезапуску кластера.
4 Tricky Questions
1. Чому в Kafka категорично заборонено ЗМЕНШУВАТИ кількість партицій у наявного топіка?
Відповідь:
- У Kafka дані всередині партиції є незмінним послідовним логом, упорядкованим за зміщеннями (offsets).
- Якби брокер дозволив зменшити кількість партицій (наприклад, з 4 до 2), виникла б нерозв’язна архітектурна дилема: що робити з даними з партицій 2 і 3?
- Об’єднати їх у партиції 0 і 1 неможливо: порушиться сувора монотонна послідовність зміщень (offsets), виникнуть колізії та дублікати індексів.
- Видалити ці дані не можна: це призведе до катастрофічної втрати даних.
- Тому утиліта
kafka-topics.sh --alterвміє лише збільшувати кількість партицій. Якщо зменшення все ж необхідне, єдиний шлях — створити новий топік і запустити міграцію даних через Kafka Connect або MirrorMaker.
2. Що станеться з порядком повідомлень у системі, якщо адміністратор збільшить кількість партицій з 3 до 6 на бойовому топіку, куди продюсери пишуть дані з ключем?
Відповідь: Порядок обробки повідомлень за конкретним бізнес-ключем буде фатально порушено! За замовчуванням продюсер Kafka обчислює номер партиції за формулою: \(\text{partition} = \text{murmur2}(\text{key}) \pmod{\text{totalPartitions}}\)
- При
totalPartitions = 3події за замовленнямorder-99стабільно потрапляли, наприклад, у партицію 1. - Щойно адміністратор збільшив кількість партицій до 6, дільник формули змінився: нові події за тим самим замовленням
order-99почнуть потрапляти в партицію 4! - У результаті подія
OrderCancelledу партиції 4 може бути оброблена консьюмером раніше, ніж подіяOrderCreatedу партиції 1. - Висновок: кількість партицій у ключових топіках необхідно розраховувати і закладати з багаторазовим запасом на етапі проєктування системи.
3. Чому Kafka не кешує повідомлення в оперативній пам’яті Java-процесу (JVM Heap), а повністю покладається на Page Cache ядра Linux?
Відповідь: Це блискуче архітектурне рішення Джея Крепса з трьох причин:
- Оверхед об’єктів Java: кожен об’єкт у Java має службовий заголовок (Mark Word + Klass Word) і вирівнювання пам’яті. Повідомлення розміром 100 байт у JVM Heap може займати до 300–400 байт. Page Cache зберігає сирі стислі бінарні байти без оверхеду.
- Паузи збирача сміття (GC STW): кешування десятків гігабайтів повідомлень у Heap призвело б до колосальних пауз Full GC, що паралізують брокер.
- Збереження даних при перезапуску брокера: якщо процес Kafka аварійно завершиться (crash / OOM /
kill -9) або перезапуститься під час оновлення, JVM Heap миттєво знищується, а Page Cache ОС залишається «гарячим» в оперативній пам’яті сервера, і брокер миттєво повертається до пікової швидкості віддавання даних.
4. Що таке «Tombstone» у топіку з політикою cleanup.policy=compact, і що станеться, якщо значення delete.retention.ms буде виставлено занадто малим?
Відповідь:
- Tombstone — це спеціальне повідомлення-маркер видалення, де
keyзаповнений, аvalue = null. Воно сигналізує брокеру про необхідність повного видалення ключа під час наступного циклу компактифікації логу. - Параметр
delete.retention.ms(за замовчуванням 24 години) визначає інтервал часу, протягом якого Tombstone гарантовано зберігається в лозі перед тим, як збирач сміття зітре його з диска. - Якщо виставити
delete.retention.msзанадто малим (наприклад, 10 секунд), то під час збою або мережевої затримки консьюмера він не встигне вичитати маркер Tombstone до його видалення з диска. У результаті консьюмер ніколи не дізнається про видалення сутності й продовжить вважати її активною у своїй локальній базі даних.
🎯 Шпаргалка для інтерв’ю
Головне про топік Kafka
- Сутність: розподілений, персистентний, незмінний журнал дозапису подій (Immutable Append-Only Log).
- Відмінність від черг: події НЕ видаляються після прочитання; їх паралельно читають незалежні групи консьюмерів.
- Топік vs Партиція: топік — логічна назва; партиція — фізична директорія на сервері брокера.
- Порядок: суворий FIFO гарантується лише всередині однієї партиції; глобального порядку в топіку немає.
- Zero-Copy (
sendfile): ядро ОС передає дані безпосередньо з Page Cache у мережевий сокет, оминаючи пам’ять JVM. - Політики очищення:
delete(за часом/розміром) таcompact(зберігає останній стан за ключем). - Партиції не можна зменшити: тільки збільшувати (але збільшення ламає порядок ключів через хешування).
- Формула надійності:
RF=3+min.insync.replicas=2+acks=all.
Топік Kafka vs Черга RabbitMQ
| Характеристика | Топік Kafka | Черга RabbitMQ | | :— | :— | :— | | Модель зберігання | Незмінний персистентний лог на диску | Черга в пам’яті (RAM), очищається за фактом ACK | | Видалення під час читання | ❌ Ні (зберігається заданий retention) | ✅ Так (видаляється після ACK) | | Перечитування (Replay) | ✅ Так (переміщення offset назад) | ❌ Ні (дані вже знищено) | | Споживачі | Багато незалежних груп консьюмерів | Конкуруючі споживачі (Compete) | | Пропускна здатність | Екстремальна (мільйони подій/сек) | Помірна (десятки тисяч повідомлень/сек) |
Червоні прапорці на співбесіді (Чого говорити не можна)
- ❌ «Топік — це звичайна черга, щойно консьюмер прочитав повідомлення, брокер його видаляє» — грубе нерозуміння архітектури Kafka.
- ❌ «Kafka гарантує ідеальний глобальний порядок повідомлень у всьому топіку» — порядок існує лише всередині конкретної партиції.
- ❌ «Якщо нам не вистачає швидкості, ми просто зменшимо кількість партицій командою alter» — партиції неможливо зменшити.
- ❌ «Kafka швидша за інші системи, тому що зберігає всі повідомлення в JVM Heap» — Kafka взагалі не зберігає повідомлення в Heap, а використовує Linux Page Cache та Zero-Copy.