Apache Kafka - Pytania Rekrutacyjne i Kompletny Przewodnik 2026

Sławomir Plamowski 23 min czytania
backend devops distributed-systems kafka messaging pytania-rekrutacyjne streaming

"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:

  1. Wszyscy consumers przestają czytać
  2. Wszystkie partycje odłączone
  3. Nowy przydział
  4. Consumers wznawiają

Cooperative rebalancing (Kafka 2.4+):

  1. Tylko zmienione przydziały są aktualizowane
  2. Consumers czytają podczas rebalancing
  3. 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:

  1. Idempotentne operacje:
// Zamiast INSERT używaj UPSERT
INSERT INTO orders (order_id, data) VALUES (?, ?)
ON CONFLICT (order_id) DO UPDATE SET data = EXCLUDED.data;
  1. Deduplication table:
// Zapisz processed message IDs
if (!processedMessages.contains(messageId)) {
    process(message);
    processedMessages.add(messageId);
    consumer.commitSync();
}
  1. Exactly-once z Kafka Streams:
props.put(StreamsConfig.PROCESSING_GUARANTEE_CONFIG,
          StreamsConfig.EXACTLY_ONCE_V2);
  1. 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:

  1. Zacznij od testowych topiców
  2. Monitoruj lag między klastrami
  3. Testuj consumer offset translation
  4. Plan dla rollback
  5. 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:

  1. Temporary zwiększ consumers
  2. Wyłącz non-essential processing
  3. Zwiększ batch size
  4. 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_id dla 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:

  1. Podstawy - topics, partitions, consumer groups, offsets
  2. Delivery semantics - at-least-once, exactly-once, idempotence
  3. Architecture - replication, ISR, Zookeeper/KRaft
  4. Operations - monitoring, lag, migrations
  5. Design - kiedy Kafka vs alternatywy, patterns

Klucz do sukcesu: zrozumienie trade-offs każdej decyzji i umiejętność uzasadnienia wyborów architektonicznych.


Zobacz też


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.

Kup pełny dostęp Zobacz bezpłatny podgląd
Powrót do blogu

Zostaw komentarz

Pamiętaj, że komentarze muszą zostać zatwierdzone przed ich opublikowaniem.