Apache Kafka - Pytania Rekrutacyjne i Kompletny Przewodnik 2026
"Wyjaśnij jak działa Kafka" - to pytanie pada na prawie każdej rozmowie o systemach rozproszonych, mikroserwisach czy data engineering. Apache Kafka stała się de facto standardem dla real-time data streaming, i rekruterzy oczekują nie tylko znajomości podstaw, ale zrozumienia partycji, consumer groups, gwarancji dostarczenia, i scenariuszy użycia.
W tym przewodniku znajdziesz 50+ pytań rekrutacyjnych z odpowiedziami, od podstaw Kafki po zaawansowane tematy jak exactly-once semantics, Kafka Streams i tuning wydajności.
Czym jest Apache Kafka - Podstawy
Odpowiedź w 30 sekund
"Apache Kafka to rozproszona platforma streamingowa do budowania real-time data pipelines. Producenci publikują wiadomości do topiców podzielonych na partycje, konsumenci w grupach odczytują je równolegle. Kafka przechowuje wiadomości na dysku z konfigurowalne retencją, umożliwiając replay i wysoką przepustowość."
Odpowiedź w 2 minuty
Kafka różni się fundamentalnie od tradycyjnych message brokerów. Zamiast usuwać wiadomości po dostarczeniu, Kafka przechowuje je jako niezmienne logi z konfigurowalna retencją (dni, tygodnie, lub bez limitu). To umożliwia scenariusze niemożliwe w RabbitMQ czy ActiveMQ.
┌─────────────────────────────────────────────────────────────────┐
│ KAFKA CLUSTER │
├─────────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Broker 1 │ │ Broker 2 │ │ Broker 3 │ │
│ │ (Leader) │ │ (Replica) │ │ (Replica) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ ┌──────┴────────────────┴────────────────┴──────┐ │
│ │ Topic: orders │ │
│ ├────────────┬────────────┬────────────┬───────┤ │
│ │ Partition 0│ Partition 1│ Partition 2│ ... │ │
│ │ [0,1,2,3] │ [0,1,2] │ [0,1,2,3,4]│ │ │
│ └────────────┴────────────┴────────────┴───────┘ │
└─────────────────────────────────────────────────────────────────┘
▲ │
│ ▼
┌──────────────┐ ┌─────────────────────┐
│ Producers │ │ Consumer Group │
│ (aplikacje) │ │ ┌─────┐ ┌─────┐ │
└──────────────┘ │ │ C1 │ │ C2 │ │
│ │P0,P1│ │ P2 │ │
│ └─────┘ └─────┘ │
└─────────────────────┘
Kluczowe koncepcje:
| Koncept | Opis |
|---|---|
| Topic | Kategoria/feed do której publikowane są wiadomości |
| Partition | Podział topicu umożliwiający równoległość |
| Broker | Serwer Kafka przechowujący partycje |
| Producer | Aplikacja publikująca wiadomości |
| Consumer | Aplikacja odczytująca wiadomości |
| Consumer Group | Zbiór konsumentów dzielących pracę |
| Offset | Pozycja wiadomości w partycji |
Architektura Kafka - Pytania Rekrutacyjne
1. Jak działa replikacja w Kafka?
Słaba odpowiedź: "Kafka kopiuje dane między brokerami."
Mocna odpowiedź:
Kafka używa modelu leader-follower dla replikacji. Każda partycja ma jednego leadera i zero lub więcej replik (followers). Wszystkie operacje read/write przechodzą przez leadera - followers pasywnie replikują.
Partition 0 Replication (replication.factor=3):
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Broker 1 │ │ Broker 2 │ │ Broker 3 │
│ LEADER │───▶│ FOLLOWER │ │ FOLLOWER │
│ [0,1,2,3] │ │ [0,1,2,3] │◀───│ [0,1,2,3] │
└─────────────┘ └─────────────┘ └─────────────┘
▲ │ │
│ │ │
Producers In-Sync In-Sync
Consumers Replica (ISR) Replica (ISR)
ISR (In-Sync Replicas) to zbiór replik, które są zsynchronizowane z leaderem. Wiadomość jest uznana za committed gdy wszystkie ISR ją potwierdzą. Parametr min.insync.replicas określa minimalną liczbę ISR wymaganą do zapisu.
Na co zwraca uwagę rekruter:
- Zrozumienie leader-follower model
- Znajomość ISR i committed messages
- Świadomość trade-off między trwałością a latencją
2. Co to jest Zookeeper i dlaczego Kafka odchodzi od niego?
Słaba odpowiedź: "Zookeeper zarządza klastrem."
Mocna odpowiedź:
Zookeeper tradycyjnie pełnił rolę centralnego koordynatora w Kafka:
- Przechowywanie metadanych klastra
- Elekcja lidera partycji
- Zarządzanie membership brokerów
- Przechowywanie konfiguracji topiców
Tradycyjna architektura:
┌──────────────────────────────────────────┐
│ Zookeeper Ensemble │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ ZK1 │ │ ZK2 │ │ ZK3 │ │
│ └──┬──┘ └──┬──┘ └──┬──┘ │
└─────┼─────────┼────────┼────────────────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│Broker 1 │ │Broker 2 │ │Broker 3 │
└─────────┘ └─────────┘ └─────────┘
KRaft (Kafka 3.x+):
┌─────────────────────────────────────────┐
│ Kafka Cluster (KRaft) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │Broker 1 │ │Broker 2 │ │Broker 3 │ │
│ │Controller│ │ │ │ │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │ │
│ └── Raft consensus dla metadanych │
└─────────────────────────────────────────┘
KRaft (Kafka Raft) - nowy tryb bez Zookeepera:
- Metadane przechowywane w wewnętrznym topicu
__cluster_metadata - Jeden broker pełni rolę active controllera
- Prostsza architektura i operacje
- Lepsza skalowalność (miliony partycji)
- Szybszy restart i recovery
Od Kafka 3.5+ KRaft jest production-ready i zalecany dla nowych instalacji.
3. Jak wybrać liczbę partycji dla topicu?
Słaba odpowiedź: "Im więcej, tym lepiej dla wydajności."
Mocna odpowiedź:
Liczba partycji to krytyczna decyzja architektoniczna, bo nie można jej zmniejszyć (tylko zwiększyć):
Czynniki do rozważenia:
1. THROUGHPUT
Partitions = Target Throughput / Throughput per Partition
Przykład: 100 MB/s / 10 MB/s per partition = 10 partitions
2. CONSUMER PARALLELISM
Max consumers = Number of partitions
(więcej consumers niż partycji = idle consumers)
3. KEY ORDERING
Wiadomości z tym samym key → ta sama partycja → zachowana kolejność
4. OVERHEAD
Więcej partycji = więcej plików, więcej pamięci, dłuższy recovery
Praktyczne guideline:
| Scenariusz | Zalecana liczba partycji |
|---|---|
| Mały topic, niski ruch | 3-6 |
| Średni ruch, ordering ważny | 12-24 |
| Wysoki throughput, wiele consumers | 50-100 |
| Event sourcing, audit log | 1 (strict ordering) |
Formuła startowa:
partitions = max(expected_throughput / partition_throughput,
max_expected_consumers)
Uwaga: Po zwiększeniu partycji, wiadomości z tym samym key mogą trafić do innych partycji - to psuje ordering dla istniejących keys.
4. Wyjaśnij różnicę między at-most-once, at-least-once i exactly-once delivery
Odpowiedź:
To trzy gwarancje dostarczenia wiadomości, każda z innymi trade-offs:
┌─────────────────────────────────────────────────────────────────┐
│ DELIVERY SEMANTICS │
├──────────────────┬──────────────────┬───────────────────────────┤
│ AT-MOST-ONCE │ AT-LEAST-ONCE │ EXACTLY-ONCE │
├──────────────────┼──────────────────┼───────────────────────────┤
│ Producer wysyła │ Producer wysyła │ Producer wysyła │
│ acks=0 │ acks=all │ acks=all + │
│ (fire & forget) │ + retries │ enable.idempotence=true │
│ │ │ + transactional.id │
├──────────────────┼──────────────────┼───────────────────────────┤
│ Wiadomość może │ Wiadomość może │ Wiadomość przetworzona │
│ być utracona │ być zduplikowana │ dokładnie raz │
├──────────────────┼──────────────────┼───────────────────────────┤
│ Najszybsze │ Średnia latencja │ Najwolniejsze │
│ Najniższa │ │ Najwyższy overhead │
│ trwałość │ │ │
├──────────────────┼──────────────────┼───────────────────────────┤
│ Use case: │ Use case: │ Use case: │
│ Metrics, logs │ Większość │ Finanse, billing, │
│ (utrata OK) │ aplikacji │ inventory │
└──────────────────┴──────────────────┴───────────────────────────┘
Konfiguracja exactly-once:
# Producer
enable.idempotence=true
acks=all
retries=Integer.MAX_VALUE
max.in.flight.requests.per.connection=5
# Dla transakcji
transactional.id=my-transactional-id
# Consumer
isolation.level=read_committed
// Transactional producer example
producer.initTransactions();
try {
producer.beginTransaction();
producer.send(new ProducerRecord<>("topic", "key", "value"));
producer.sendOffsetsToTransaction(offsets, consumerGroupId);
producer.commitTransaction();
} catch (Exception e) {
producer.abortTransaction();
}
Consumer Groups i Offset Management
5. Jak działa rebalancing w consumer group?
Słaba odpowiedź: "Kafka automatycznie rozdziela partycje."
Mocna odpowiedź:
Rebalancing to proces ponownego przydziału partycji do konsumentów. Następuje gdy:
- Nowy consumer dołącza do grupy
- Consumer opuszcza grupę (crash, shutdown)
- Dodano nowe partycje do topicu
- Consumer nie wysłał heartbeat w czasie
session.timeout.ms
PRZED rebalancing (2 consumers, 4 partitions):
┌─────────────┐ ┌─────────────┐
│ Consumer 1 │ │ Consumer 2 │
│ P0, P1 │ │ P2, P3 │
└─────────────┘ └─────────────┘
Consumer 3 dołącza → REBALANCING
PO rebalancing (3 consumers, 4 partitions):
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Consumer 1 │ │ Consumer 2 │ │ Consumer 3 │
│ P0, P1 │ │ P2 │ │ P3 │
└─────────────┘ └─────────────┘ └─────────────┘
Strategie przydziału partycji:
| Strategia | Opis | Use case |
|---|---|---|
| RangeAssignor | Kolejne partycje do kolejnych consumers | Domyślna |
| RoundRobinAssignor | Równomierne rozłożenie | Multi-topic |
| StickyAssignor | Minimalizuje zmiany przy rebalancing | Statefull consumers |
| CooperativeStickyAssignor | Incremental rebalancing | Kafka 2.4+ |
Problem stop-the-world:
Tradycyjny (Eager) rebalancing:
- Wszyscy consumers przestają czytać
- Wszystkie partycje odłączone
- Nowy przydział
- Consumers wznawiają
Cooperative rebalancing (Kafka 2.4+):
- Tylko zmienione przydziały są aktualizowane
- Consumers czytają podczas rebalancing
- Mniejszy impact na throughput
# Włączenie cooperative rebalancing
partition.assignment.strategy=org.apache.kafka.clients.consumer.CooperativeStickyAssignor
6. Jak zarządzać offsetami w Kafka?
Odpowiedź:
Offset to pozycja wiadomości w partycji. Kafka przechowuje committed offsets w wewnętrznym topicu __consumer_offsets.
Partition 0:
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ → wiadomości
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
▲ ▲ ▲
│ │ │
committed current log-end
offset position offset
Auto commit vs manual commit:
// AUTO COMMIT (domyślne)
props.put("enable.auto.commit", "true");
props.put("auto.commit.interval.ms", "5000");
// MANUAL COMMIT - pełna kontrola
props.put("enable.auto.commit", "false");
// Synchronous commit (blokuje do potwierdzenia)
consumer.commitSync();
// Asynchronous commit (nie blokuje)
consumer.commitAsync((offsets, exception) -> {
if (exception != null) {
log.error("Commit failed", exception);
}
});
// Commit specific offset
consumer.commitSync(Map.of(
new TopicPartition("topic", 0),
new OffsetAndMetadata(lastProcessedOffset + 1)
));
auto.offset.reset - co gdy brak committed offset:
| Wartość | Zachowanie |
|---|---|
earliest |
Czytaj od początku partycji |
latest |
Czytaj tylko nowe wiadomości |
none |
Rzuć wyjątek |
Best practice:
- Manual commit dla at-least-once semantics
- Commit po successful processing, nie przed
- Dla exactly-once: transactional producer z
sendOffsetsToTransaction
7. Co się stanie gdy consumer przetworzy wiadomość ale crash przed commit?
Odpowiedź:
To klasyczny scenariusz prowadzący do duplikatów przy at-least-once semantics:
Timeline:
1. Consumer czyta wiadomość offset=5
2. Consumer przetwarza wiadomość (np. zapis do DB)
3. CRASH przed commitSync()
4. Consumer restartuje
5. Kafka zwraca wiadomość od committed offset=4
6. Consumer przetwarza offset=5 PONOWNIE → DUPLIKAT
┌─────────────────────────────────────────────────────┐
│ DB zapisane: [msg4, msg5] │
│ Kafka committed: offset=4 │
│ → Po restart: msg5 przetworzone dwukrotnie │
└─────────────────────────────────────────────────────┘
Strategie radzenia sobie z duplikatami:
- Idempotentne operacje:
// Zamiast INSERT używaj UPSERT
INSERT INTO orders (order_id, data) VALUES (?, ?)
ON CONFLICT (order_id) DO UPDATE SET data = EXCLUDED.data;
- Deduplication table:
// Zapisz processed message IDs
if (!processedMessages.contains(messageId)) {
process(message);
processedMessages.add(messageId);
consumer.commitSync();
}
- Exactly-once z Kafka Streams:
props.put(StreamsConfig.PROCESSING_GUARANTEE_CONFIG,
StreamsConfig.EXACTLY_ONCE_V2);
- Outbox pattern:
Transaction {
1. Zapisz do business table
2. Zapisz do outbox table
}
// Osobny proces publikuje z outbox do Kafka
Producers i Performance
8. Jak zoptymalizować wydajność producenta Kafka?
Odpowiedź:
Producent Kafka ma wiele parametrów wpływających na throughput vs latencję:
┌─────────────────────────────────────────────────────────────────┐
│ PRODUCER INTERNALS │
├─────────────────────────────────────────────────────────────────┤
│ │
│ send() → RecordAccumulator → Sender Thread → Broker │
│ (batching) (network) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Batch 1 │ │ Batch 2 │ │ Batch 3 │ │
│ │ (partition 0)│ │ (partition 1)│ │ (partition 2)│ │
│ │ [msg1,msg2] │ │ [msg3] │ │ [msg4,msg5] │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Kluczowe parametry:
| Parametr | Default | High Throughput | Low Latency |
|---|---|---|---|
batch.size |
16KB | 64KB-128KB | 0 |
linger.ms |
0 | 10-100ms | 0 |
buffer.memory |
32MB | 64MB+ | 32MB |
compression.type |
none | lz4/zstd | none |
acks |
all | 1 | 1 |
# High throughput configuration
batch.size=65536
linger.ms=20
compression.type=lz4
buffer.memory=67108864
acks=1
# Low latency configuration
batch.size=0
linger.ms=0
acks=1
Compression comparison:
| Algorytm | Ratio | CPU | Throughput |
|---|---|---|---|
| none | 1x | lowest | baseline |
| lz4 | 2-3x | low | highest |
| snappy | 2-3x | low | high |
| zstd | 4-5x | medium | medium |
| gzip | 5-6x | high | lowest |
Best practice: Używaj lz4 lub zstd - kompresja zwiększa throughput przez redukcję I/O.
9. Co to jest sticky partitioner i kiedy go używać?
Odpowiedź:
Sticky partitioner (Kafka 2.4+) to optymalizacja dla wiadomości bez klucza:
BEZ sticky partitioner (round-robin):
send(null, msg1) → Partition 0 ─┐
send(null, msg2) → Partition 1 ─┼─ Małe batche, częste wysyłki
send(null, msg3) → Partition 2 ─┘
ZE sticky partitioner:
send(null, msg1) → Partition 0 ─┐
send(null, msg2) → Partition 0 ─┼─ Jeden duży batch, rzadsze wysyłki
send(null, msg3) → Partition 0 ─┘
(zmiana partycji gdy batch pełny lub linger.ms upłynął)
Korzyści:
- Większe batche = lepsza kompresja
- Mniej requestów do brokerów
- Wyższy throughput
Włączenie:
# Kafka 2.4+
partitioner.class=org.apache.kafka.clients.producer.internals.DefaultPartitioner
# (sticky jest domyślny dla null keys)
# Wymuszenie round-robin jeśli potrzebny
partitioner.class=org.apache.kafka.clients.producer.RoundRobinPartitioner
10. Jak działa idempotentny producer?
Odpowiedź:
Idempotentny producer gwarantuje exactly-once delivery do pojedynczej partycji, nawet przy retry:
BEZ idempotence:
Producer → send(msg) → Broker (zapisuje)
← timeout (ACK zgubiony)
Producer → retry(msg) → Broker (zapisuje PONOWNIE)
Rezultat: DUPLIKAT
Z idempotence:
Producer → send(msg, PID=1, seq=5) → Broker (zapisuje)
← timeout
Producer → retry(msg, PID=1, seq=5) → Broker (seq already seen, skip)
Rezultat: JEDNA wiadomość
Jak to działa:
┌─────────────────────────────────────────────────────┐
│ Producer │
│ PID (Producer ID): 1000 │
│ │
│ Partition 0: sequence = 45 │
│ Partition 1: sequence = 23 │
│ │
│ Każda wiadomość: (PID, partition, sequence) │
│ Broker odrzuca duplikaty na podstawie sequence │
└─────────────────────────────────────────────────────┘
Konfiguracja:
enable.idempotence=true
# Automatycznie ustawia:
# acks=all
# retries=Integer.MAX_VALUE
# max.in.flight.requests.per.connection=5
Ograniczenia:
- Działa per partition (nie cross-partition)
- Dla cross-partition exactly-once potrzebne transakcje
- Niewielki overhead (PID + sequence tracking)
Kafka Streams i Processing
11. Czym jest Kafka Streams i kiedy go używać?
Słaba odpowiedź: "To biblioteka do przetwarzania danych z Kafka."
Mocna odpowiedź:
Kafka Streams to biblioteka kliencka (nie osobny cluster) do stream processing z dokładnie-raz semantyką:
┌─────────────────────────────────────────────────────────────────┐
│ KAFKA STREAMS APP │
├─────────────────────────────────────────────────────────────────┤
│ Input Topic → Stream Processing → Output Topic │
│ │
│ ┌─────────┐ ┌─────────────────────┐ ┌─────────┐ │
│ │ orders │───▶│ KStream/KTable │───▶│ alerts │ │
│ │ (topic) │ │ - filter │ │ (topic) │ │
│ └─────────┘ │ - map │ └─────────┘ │
│ │ - join │ │
│ ┌─────────┐ │ - aggregate │ ┌─────────┐ │
│ │ users │───▶│ - windowing │───▶│ metrics │ │
│ │ (topic) │ └─────────────────────┘ │ (topic) │ │
│ └─────────┘ │ └─────────┘ │
│ ▼ │
│ ┌───────────────┐ │
│ │ State Store │ │
│ │ (RocksDB) │ │
│ └───────────────┘ │
└─────────────────────────────────────────────────────────────────┘
KStream vs KTable:
| Aspekt | KStream | KTable |
|---|---|---|
| Semantyka | Event stream | Changelog / latest value per key |
| Interpretacja | Każdy rekord to nowy event | Rekord to update dla key |
| Use case | Events, logs, transactions | Lookup data, aggregations |
| Analog SQL | INSERT stream | UPDATE table |
// KStream - stream of events
KStream<String, Order> orders = builder.stream("orders");
orders.filter((key, order) -> order.getAmount() > 1000)
.to("large-orders");
// KTable - compacted view
KTable<String, User> users = builder.table("users");
users.filter((key, user) -> user.isActive())
.toStream()
.to("active-users");
// Join KStream with KTable
orders.join(users,
(order, user) -> new EnrichedOrder(order, user))
.to("enriched-orders");
Kafka Streams vs Flink/Spark:
| Cecha | Kafka Streams | Flink/Spark |
|---|---|---|
| Deployment | Library (JAR) | Separate cluster |
| Scaling | Consumer group | Cluster manager |
| State | Local (RocksDB) | Distributed |
| Exactly-once | Native | Requires setup |
| Complexity | Low | High |
12. Jak działają okna czasowe (windowing) w Kafka Streams?
Odpowiedź:
Windowing grupuje eventy po czasie dla agregacji:
┌─────────────────────────────────────────────────────────────────┐
│ WINDOW TYPES │
├─────────────────────────────────────────────────────────────────┤
│ │
│ TUMBLING (non-overlapping): │
│ |─────────|─────────|─────────| │
│ │ Window1 │ Window2 │ Window3 │ │
│ |─────────|─────────|─────────| │
│ 0-10min 10-20min 20-30min │
│ │
│ HOPPING (overlapping): │
│ |─────────────| │
│ |─────────────| │
│ |─────────────| │
│ Size: 10min, Advance: 5min │
│ │
│ SLIDING (triggered by events): │
│ ──●────●───●──●─────●─── │
│ |──────| │
│ |──────| │
│ Window around each event │
│ │
│ SESSION (gap-based): │
│ ─●●●────────────●●─────────●●●●── │
│ |───| inactivity |──| |────| │
│ Session ends after inactivity gap │
└─────────────────────────────────────────────────────────────────┘
Przykłady użycia:
// Tumbling window - orders per hour
orders.groupByKey()
.windowedBy(TimeWindows.ofSizeWithNoGrace(Duration.ofHours(1)))
.count()
.toStream()
.to("hourly-order-counts");
// Hopping window - moving average
sales.groupByKey()
.windowedBy(TimeWindows.ofSizeWithNoGrace(Duration.ofMinutes(10))
.advanceBy(Duration.ofMinutes(1)))
.reduce((a, b) -> average(a, b));
// Session window - user sessions
clicks.groupByKey()
.windowedBy(SessionWindows.ofInactivityGapWithNoGrace(Duration.ofMinutes(30)))
.count()
.toStream()
.to("session-clicks");
Event time vs Processing time:
// Event time (from message timestamp)
Consumed.with(Serdes.String(), Serdes.String())
.withTimestampExtractor(new WallclockTimestampExtractor());
// Custom timestamp from payload
public class OrderTimestampExtractor implements TimestampExtractor {
@Override
public long extract(ConsumerRecord<Object, Object> record, long partitionTime) {
Order order = (Order) record.value();
return order.getCreatedAt().toEpochMilli();
}
}
Operational Questions
13. Jak monitorować Kafka cluster?
Odpowiedź:
Kluczowe metryki do monitorowania:
┌─────────────────────────────────────────────────────────────────┐
│ KAFKA METRICS │
├────────────────────────┬────────────────────────────────────────┤
│ BROKER METRICS │ Description │
├────────────────────────┼────────────────────────────────────────┤
│ UnderReplicatedPartitions │ Partycje z insufficient replicas │
│ ActiveControllerCount │ Powinno być 1 w klastrze │
│ OfflinePartitionsCount │ Niedostępne partycje (ALERT!) │
│ IsrShrinksPerSec │ Repliki wypadające z ISR │
│ BytesInPerSec │ Throughput przychodzący │
│ BytesOutPerSec │ Throughput wychodzący │
│ RequestsPerSec │ Request rate │
│ NetworkProcessorAvgIdlePercent │ CPU network thread │
├────────────────────────┼────────────────────────────────────────┤
│ PRODUCER METRICS │ │
├────────────────────────┼────────────────────────────────────────┤
│ record-send-rate │ Messages/sec │
│ record-error-rate │ Failed sends │
│ request-latency-avg │ Średnia latencja │
│ batch-size-avg │ Rozmiar batchy │
├────────────────────────┼────────────────────────────────────────┤
│ CONSUMER METRICS │ │
├────────────────────────┼────────────────────────────────────────┤
│ records-lag │ Opóźnienie względem producenta │
│ records-lag-max │ Max lag (ALERT!) │
│ fetch-rate │ Fetche/sec │
│ commit-rate │ Commits/sec │
└────────────────────────┴────────────────────────────────────────┘
Alerting thresholds:
# Przykładowa konfiguracja alertów
alerts:
- name: UnderReplicatedPartitions
condition: > 0
severity: warning
- name: OfflinePartitionsCount
condition: > 0
severity: critical
- name: ConsumerLag
condition: > 10000
severity: warning
- name: ConsumerLag
condition: > 100000
severity: critical
Narzędzia:
- Prometheus + Grafana - standard dla metrics
- Kafka Manager / AKHQ - UI do zarządzania
- Burrow - consumer lag monitoring (LinkedIn)
- Cruise Control - auto-balancing (LinkedIn)
14. Jak przeprowadzić migrację danych między klastrami Kafka?
Odpowiedź:
Dwa główne podejścia:
1. MirrorMaker 2 (MM2) - zalecane:
┌─────────────────┐ ┌─────────────────┐
│ Source Cluster │ │ Target Cluster │
│ (DC1) │ │ (DC2) │
├─────────────────┤ ├─────────────────┤
│ topic-a │──MM2───▶│ dc1.topic-a │
│ topic-b │ │ dc1.topic-b │
└─────────────────┘ └─────────────────┘
# mm2.properties
clusters = source, target
source.bootstrap.servers = source-kafka:9092
target.bootstrap.servers = target-kafka:9092
source->target.enabled = true
source->target.topics = .*
# Checkpoint dla consumer offset sync
checkpoints.topic.replication.factor = 3
sync.group.offsets.enabled = true
MM2 features:
- Automatic topic creation
- Consumer offset translation
- ACL replication
- Active-active support
2. Kafka Connect z Replicator:
{
"name": "replicator",
"config": {
"connector.class": "io.confluent.connect.replicator.ReplicatorSourceConnector",
"src.kafka.bootstrap.servers": "source:9092",
"dest.kafka.bootstrap.servers": "target:9092",
"topic.whitelist": "orders,users",
"topic.rename.format": "${topic}.replica"
}
}
Best practices dla migracji:
- Zacznij od testowych topiców
- Monitoruj lag między klastrami
- Testuj consumer offset translation
- Plan dla rollback
- Cutover w low-traffic window
15. Jak rozwiązać problem "consumer lag"?
Odpowiedź:
Consumer lag to różnica między latest offset (log-end) a committed offset:
Partition:
[0][1][2][3][4][5][6][7][8][9][10][11][12][13][14]
▲ ▲
committed=5 log-end=14
LAG = 14 - 5 = 9 messages
Przyczyny i rozwiązania:
| Przyczyna | Rozwiązanie |
|---|---|
| Slow processing | Optymalizuj logikę, async processing |
| Too few consumers | Dodaj consumers (max = partitions) |
| Too few partitions | Zwiększ partycje (trudne!) |
| GC pauses | Tune JVM, zwiększ heap |
| Network issues | Check bandwidth, latency |
| Rebalancing storms | Cooperative sticky assignor |
| Slow external calls | Circuit breaker, bulkhead |
Strategie redukcji lag:
// 1. Zwiększ fetch size
props.put("fetch.min.bytes", "1048576"); // 1MB
props.put("fetch.max.wait.ms", "500");
props.put("max.partition.fetch.bytes", "10485760"); // 10MB
// 2. Batch processing
List<ConsumerRecord<String, String>> batch = new ArrayList<>();
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
batch.addAll(records);
if (batch.size() >= 1000 || timeout()) {
processBatch(batch);
consumer.commitSync();
batch.clear();
}
}
// 3. Parallel processing within partition (ostrożnie z ordering!)
records.forEach(record -> executor.submit(() -> process(record)));
// 4. Skip to latest (emergency, data loss!)
consumer.seekToEnd(consumer.assignment());
Catching up strategy:
- Temporary zwiększ consumers
- Wyłącz non-essential processing
- Zwiększ batch size
- Po catch-up wróć do normal config
Kafka Connect i Integracje
16. Czym jest Kafka Connect i kiedy go używać?
Odpowiedź:
Kafka Connect to framework do integracji Kafka z zewnętrznymi systemami bez pisania kodu:
┌─────────────────────────────────────────────────────────────────┐
│ KAFKA CONNECT │
├─────────────────────────────────────────────────────────────────┤
│ │
│ SOURCES SINKS │
│ ┌──────────┐ ┌──────────┐ │
│ │ Database │──┐ ┌──▶│ Database │ │
│ └──────────┘ │ │ └──────────┘ │
│ ┌──────────┐ │ ┌───────┐ │ ┌──────────┐ │
│ │ Files │──┼──▶│ KAFKA │──┼──▶│ Elastic │ │
│ └──────────┘ │ └───────┘ │ └──────────┘ │
│ ┌──────────┐ │ │ ┌──────────┐ │
│ │ APIs │──┘ └──▶│ S3/HDFS │ │
│ └──────────┘ └──────────┘ │
│ │
│ Source Connectors: Sink Connectors: │
│ - Debezium (CDC) - JDBC Sink │
│ - JDBC Source - Elasticsearch Sink │
│ - S3 Source - S3 Sink │
│ - FileStream - HDFS Sink │
└─────────────────────────────────────────────────────────────────┘
Przykład JDBC Source Connector:
{
"name": "jdbc-source-users",
"config": {
"connector.class": "io.confluent.connect.jdbc.JdbcSourceConnector",
"connection.url": "jdbc:postgresql://db:5432/app",
"connection.user": "user",
"connection.password": "${secrets:db-password}",
"table.whitelist": "users,orders",
"mode": "timestamp+incrementing",
"timestamp.column.name": "updated_at",
"incrementing.column.name": "id",
"topic.prefix": "db-",
"poll.interval.ms": "1000"
}
}
Debezium (CDC) example:
{
"name": "postgres-cdc",
"config": {
"connector.class": "io.debezium.connector.postgresql.PostgresConnector",
"database.hostname": "postgres",
"database.port": "5432",
"database.user": "debezium",
"database.password": "dbz",
"database.dbname": "app",
"database.server.name": "dbserver1",
"table.include.list": "public.orders",
"plugin.name": "pgoutput"
}
}
Standalone vs Distributed mode:
| Aspekt | Standalone | Distributed |
|---|---|---|
| Workers | Single | Multiple |
| Config | File | REST API |
| Fault tolerance | None | Automatic |
| Use case | Development | Production |
17. Jak działa Debezium Change Data Capture?
Odpowiedź:
Debezium czyta database transaction log i publikuje zmiany jako events:
┌─────────────────────────────────────────────────────────────────┐
│ DEBEZIUM CDC │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────┐ │
│ │ PostgreSQL │ │
│ │ ┌──────────────┐ │ │
│ │ │ WAL (Write │ │ ┌──────────┐ ┌─────────┐ │
│ │ │ Ahead Log) │──┼────▶│ Debezium │────▶│ Kafka │ │
│ │ └──────────────┘ │ │Connector │ │ Topics │ │
│ │ │ └──────────┘ └─────────┘ │
│ │ INSERT → WAL │ dbserver1. │
│ │ UPDATE → WAL │ public.orders │
│ │ DELETE → WAL │ │
│ └────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Event structure:
{
"before": {
"id": 1,
"status": "pending",
"amount": 100
},
"after": {
"id": 1,
"status": "completed",
"amount": 100
},
"source": {
"version": "2.4.0",
"connector": "postgresql",
"name": "dbserver1",
"ts_ms": 1704672000000,
"db": "app",
"schema": "public",
"table": "orders"
},
"op": "u",
"ts_ms": 1704672000100
}
Operation types:
-
c= create (INSERT) -
u= update (UPDATE) -
d= delete (DELETE) -
r= read (snapshot)
Use cases:
- Cache invalidation
- Search index sync
- Microservices data sync
- Event sourcing
- Audit logging
- Data lake ingestion
Design Questions
18. Zaprojektuj system powiadomień w czasie rzeczywistym z Kafka
Odpowiedź:
┌─────────────────────────────────────────────────────────────────┐
│ REAL-TIME NOTIFICATION SYSTEM │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Event Sources Kafka Consumers │
│ ┌──────────┐ │
│ │ Orders │───┐ ┌─────────────┐ │
│ │ Service │ │ │ user-events │ ┌─────────────┐ │
│ └──────────┘ │ │ (partitioned│────▶│ Notification│ │
│ ┌──────────┐ │ │ by user_id)│ │ Router │ │
│ │ Payment │───┼───────▶├─────────────┤ └──────┬──────┘ │
│ │ Service │ │ │notifications│ │ │
│ └──────────┘ │ │-to-send │◀───────────┘ │
│ ┌──────────┐ │ └─────────────┘ │
│ │ Shipping │───┘ │ │
│ │ Service │ ▼ │
│ └──────────┘ ┌─────────────────────┐ │
│ │ Notification Workers │ │
│ ├─────────┬───────────┤ │
│ │ Email │ Push │ │
│ │ Worker │ Worker │ │
│ └─────────┴───────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ SendGrid │ │ Firebase │ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────────┘
Topic design:
user-events (partitioned by user_id)
├── key: user_id
├── value: {event_type, payload, timestamp}
└── retention: 7 days
notifications-to-send (partitioned by user_id)
├── key: user_id
├── value: {channel, template, data, priority}
└── retention: 24 hours
notification-status (compacted)
├── key: notification_id
├── value: {status, sent_at, delivered_at}
└── cleanup.policy: compact
Notification Router (Kafka Streams):
KStream<String, UserEvent> events = builder.stream("user-events");
events
.filter((userId, event) -> shouldNotify(event))
.flatMap((userId, event) -> {
User user = userTable.get(userId);
return buildNotifications(user, event);
})
.to("notifications-to-send");
Key decisions:
- Partition by
user_iddla ordering per user - Osobne topici dla różnych priorytetów
- Dead letter queue dla failed notifications
- Idempotency przez
notification_id - Rate limiting per user w consumer
19. Kafka vs RabbitMQ vs AWS SQS - kiedy który wybrać?
Odpowiedź:
┌─────────────────────────────────────────────────────────────────┐
│ MESSAGING COMPARISON │
├─────────────┬─────────────┬─────────────┬───────────────────────┤
│ │ KAFKA │ RABBITMQ │ AWS SQS │
├─────────────┼─────────────┼─────────────┼───────────────────────┤
│ Model │ Log-based │ Queue-based │ Queue-based │
│ │ Pull │ Push/Pull │ Pull │
├─────────────┼─────────────┼─────────────┼───────────────────────┤
│ Retention │ Configurable│ Until ACK │ 14 days max │
│ │ (days/∞) │ │ │
├─────────────┼─────────────┼─────────────┼───────────────────────┤
│ Replay │ ✅ Yes │ ❌ No │ ❌ No │
├─────────────┼─────────────┼─────────────┼───────────────────────┤
│ Ordering │ Per partition│ Per queue │ FIFO queues only │
├─────────────┼─────────────┼─────────────┼───────────────────────┤
│ Throughput │ Millions/s │ Thousands/s │ Thousands/s │
├─────────────┼─────────────┼─────────────┼───────────────────────┤
│ Latency │ ~5-10ms │ ~1ms │ ~20-100ms │
├─────────────┼─────────────┼─────────────┼───────────────────────┤
│ Routing │ Basic │ Advanced │ Basic │
│ │ (key-based) │ (exchanges) │ │
├─────────────┼─────────────┼─────────────┼───────────────────────┤
│ Operations │ Complex │ Medium │ None (managed) │
├─────────────┼─────────────┼─────────────┼───────────────────────┤
│ Cost │ Infrastructure│ Infrastructure│ Pay-per-message │
└─────────────┴─────────────┴─────────────┴───────────────────────┘
Decision matrix:
| Requirement | Choose |
|---|---|
| High throughput (>100K/s) | Kafka |
| Event replay / audit | Kafka |
| Stream processing | Kafka |
| Complex routing | RabbitMQ |
| Low latency (<5ms) | RabbitMQ |
| Request-reply pattern | RabbitMQ |
| Serverless, no ops | SQS |
| AWS native integration | SQS |
| Simple task queue | SQS or RabbitMQ |
| Event sourcing | Kafka |
Hybrid approach:
Kafka (event backbone)
└── Event streaming, CDC, analytics
RabbitMQ (task distribution)
└── Work queues, RPC, routing
SQS (AWS integrations)
└── Lambda triggers, decoupling AWS services
Zaawansowane Pytania
20. Jak zaimplementować transakcje across Kafka i bazę danych?
Odpowiedź:
Transakcje cross-system to złożony problem. Główne podejścia:
1. Outbox Pattern (zalecane):
┌─────────────────────────────────────────────────────────────────┐
│ OUTBOX PATTERN │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ Database Transaction │ │
│ │ ┌──────────────┐ ┌──────────────────┐│ │
│ │ │ Orders Table │ │ Outbox Table ││ │
│ │ │ INSERT order │ │ INSERT event ││ │
│ │ └──────────────┘ └──────────────────┘│ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ Outbox Relay (Debezium or Poller) │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────┐ │
│ │ Kafka │ │
│ │ order-events │ │
│ └───────────────┘ │
└─────────────────────────────────────────────────────────────────┘
-- Outbox table
CREATE TABLE outbox (
id UUID PRIMARY KEY,
aggregate_type VARCHAR(255),
aggregate_id VARCHAR(255),
event_type VARCHAR(255),
payload JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
-- In single transaction
BEGIN;
INSERT INTO orders (id, user_id, amount) VALUES (...);
INSERT INTO outbox (id, aggregate_type, aggregate_id, event_type, payload)
VALUES (gen_random_uuid(), 'Order', order_id, 'OrderCreated', order_json);
COMMIT;
2. Listen-to-yourself (Change Data Capture):
App → DB (write) → CDC (Debezium) → Kafka → App (read own events)
3. Saga Pattern dla distributed transactions:
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Order │───▶│ Payment │───▶│ Shipping │
│ Service │ │ Service │ │ Service │
└────────────┘ └────────────┘ └────────────┘
│ │ │
│ Compensation on failure: │
│◀────────────────┼─────────────────│
│ Cancel Order │ Refund │
// Saga coordinator
class OrderSaga {
void execute(Order order) {
try {
publish("payment-commands", new ReservePayment(order));
// Wait for PaymentReserved event
publish("inventory-commands", new ReserveInventory(order));
// Wait for InventoryReserved event
publish("order-commands", new ConfirmOrder(order));
} catch (SagaFailure e) {
// Compensating transactions
compensate(e.getCompletedSteps());
}
}
}
21. Jak zapewnić ordering w Kafka przy wielu partycjach?
Odpowiedź:
Kafka gwarantuje ordering tylko w ramach jednej partycji. Strategie dla global ordering:
1. Single partition (proste, nie skaluje):
# Topic z 1 partycją
kafka-topics --create --topic strict-order --partitions 1
2. Key-based partitioning (ordering per entity):
// Wszystkie eventy dla tego samego order_id → ta sama partycja
producer.send(new ProducerRecord<>(
"orders",
orderId, // key
orderEvent
));
// Partitioner: hash(key) % num_partitions
3. Sequence numbers w message:
public class OrderedMessage {
String entityId;
long sequenceNumber; // monotonically increasing per entity
Object payload;
}
// Consumer sortuje przed processing
Map<String, PriorityQueue<OrderedMessage>> buffers = new HashMap<>();
4. Kafka Streams z timestamp-based ordering:
// Kafka Streams utrzymuje ordering przez punctuators
stream.transform(() -> new OrderingTransformer());
class OrderingTransformer implements Transformer<K, V, KeyValue<K, V>> {
private KeyValueStore<K, List<V>> buffer;
@Override
public void init(ProcessorContext context) {
context.schedule(
Duration.ofMillis(100),
PunctuationType.WALL_CLOCK_TIME,
this::emitOrdered
);
}
}
5. External sequencer (złożone):
┌──────────────┐ ┌───────────────┐ ┌──────────────┐
│ Producer │────▶│ Sequencer │────▶│ Kafka │
│ │ │ (assigns seq) │ │ (1 partition)│
└──────────────┘ └───────────────┘ └──────────────┘
Trade-offs:
| Approach | Ordering | Scalability | Complexity |
|---|---|---|---|
| Single partition | Global | None | Low |
| Key-based | Per key | High | Low |
| Sequence in message | Flexible | High | Medium |
| External sequencer | Global | Medium | High |
Praktyczne Zadania
Zadanie 1: Debug consumer lag
Consumer group order-processor ma lag 500K messages. Jak to zbadasz i rozwiążesz?
Podejście:
# 1. Sprawdź lag per partition
kafka-consumer-groups --bootstrap-server kafka:9092 \
--group order-processor --describe
# 2. Sprawdź consumer count vs partition count
# 3. Sprawdź consumer metrics (processing time, fetch rate)
# 4. Sprawdź czy nie ma częstych rebalancing
# 5. Analizuj logi consumer pod kątem exceptions
Zadanie 2: Zaprojektuj exactly-once pipeline
Stwórz pipeline: Postgres (orders) → Kafka → Elasticsearch
Rozwiązanie:
Debezium (CDC) → Kafka (orders topic) → Kafka Connect ES Sink
- Debezium zapewnia at-least-once z DB
- ES sink z idempotent upserts (document ID = order ID)
- Rezultat: effectively exactly-once
Zadanie 3: Topic retention i compaction
Kiedy użyjesz delete vs compact cleanup policy?
Odpowiedź:
-
delete: Events, logs, time-series (retention by time/size) -
compact: Latest state per key (user preferences, config) -
delete,compact: Both - compact recent, delete old
Podsumowanie
Apache Kafka to fundament nowoczesnych systemów rozproszonych. Na rozmowie rekrutacyjnej oczekuj pytań o:
- Podstawy - topics, partitions, consumer groups, offsets
- Delivery semantics - at-least-once, exactly-once, idempotence
- Architecture - replication, ISR, Zookeeper/KRaft
- Operations - monitoring, lag, migrations
- Design - kiedy Kafka vs alternatywy, patterns
Klucz do sukcesu: zrozumienie trade-offs każdej decyzji i umiejętność uzasadnienia wyborów architektonicznych.
Zobacz też
- Kompletny Przewodnik DevOps Engineer
- Wzorce i Architektura Backend
- Docker - Pytania Rekrutacyjne
- Kubernetes - Pytania Rekrutacyjne
Artykuł przygotowany przez zespół Flipcards - tworzymy materiały do nauki programowania i przygotowania do rozmów rekrutacyjnych.
Chcesz więcej pytań rekrutacyjnych?
To tylko jeden temat z naszego kompletnego przewodnika po rozmowach rekrutacyjnych. Uzyskaj dostęp do 800+ pytań z 13 technologii.
