Apache Kafka w Data Engineering: Partycje, Consumer Groups i Pipeline'y Strumieniowe

Kompleksowy przewodnik po Apache Kafka dla inzynierow danych. Architektura streamingu w trybie KRaft, strategie partycjonowania, consumer groups, Kafka Connect z Debezium, semantyka exactly-once, Share Groups (KIP-932) oraz pytania rekrutacyjne z przykladami kodu Python.

Apache Kafka for Data Engineers

Apache Kafka stanowi rdzen wiekszosci nowoczesnych stosow technologicznych w data engineeringu, obslugujac biliony zdarzen dziennie w organizacjach kazdej wielkosci. Wraz z wydaniem Kafka 4.x, platforma dziala calkowicie w oparciu o KRaft (bez zaleznosci od ZooKeepera), co znaczaco uproscilo operacje administracyjne przy zachowaniu tych samych gwarancji niezawodnosci, ktore uczynialy z niej standard branzy w dziedzinie pipeline'ow przetwarzania danych w czasie rzeczywistym. Niniejszy artykul omawia kluczowe aspekty Apache Kafka z perspektywy inzyniera danych -- od architektury partycji, przez zarzadzanie offsetami, po transakcyjne pipeline'y exactly-once -- z produkcyjnymi przykladami kodu i pytaniami przygotowujacymi do rozmow rekrutacyjnych z zakresu data engineeringu.

Kafka 4.x i tryb KRaft

Od wersji Apache Kafka 4.0 ZooKeeper zostal calkowicie wyeliminowany z architektury. Tryb KRaft (Kafka Raft) obsluguje metadane klastra natywnie, co redukuje liczbe komponentow do wdrozenia i administracji. Kazdy nowy klaster Kafka powinien byc uruchamiany wylacznie w trybie KRaft.

Architektura strumieniowa Kafka dla pipeline'ow danych

Podstawowym wymaganiem przy projektowaniu pipeline'ow strumieniowych opartych na Kafce jest prawidlowe skonfigurowanie klastra. Od wersji 4.0 ZooKeeper nie jest juz potrzebny -- tryb KRaft integruje mechanizm konsensusu bezposrednio w brokerach, eliminujac zewnetrzna zaleznosc i redukujac zlozonosc operacyjna.

Ponizszy plik Docker Compose definiuje klaster Kafka 4.2 zlozony z trzech wezlow w trybie KRaft. Kazdy wezel pelni jednoczesnie role brokera i kontrolera, co jest odpowiednia konfiguracja dla srodowisk deweloperskich i testowych:

yaml
# docker-compose.yml — Kafka 4.x KRaft cluster (no ZooKeeper)
services:
  kafka-1:
    image: apache/kafka:4.2.0
    environment:
      KAFKA_NODE_ID: 1
      KAFKA_PROCESS_ROLES: broker,controller
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka-1:9093,2@kafka-2:9093,3@kafka-3:9093
      KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
      KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_LOG_DIRS: /var/lib/kafka/data
      KAFKA_NUM_PARTITIONS: 6
      KAFKA_DEFAULT_REPLICATION_FACTOR: 3
      KAFKA_MIN_INSYNC_REPLICAS: 2
    ports:
      - "9092:9092"

Zmienna KAFKA_CONTROLLER_QUORUM_VOTERS okresla wezly uczestniczace w kworum Raft. Wspolczynnik replikacji ustawiony na 3 w polaczeniu z minimum 2 zsynchronizowanymi replikami (MIN_INSYNC_REPLICAS) oznacza, ze klaster toleruje utrate jednego wezla bez przerwy w dzialaniu i bez utraty danych. Szesc domyslnych partycji (KAFKA_NUM_PARTITIONS: 6) pozwala na zrownoleglenie przetwarzania po stronie konsumentow juz na etapie konfiguracji klastra. W srodowiskach produkcyjnych zaleca sie separacje rol kontrolera i brokera na dedykowane maszyny, aby odizolowac zarzadzanie metadanymi od obslugi ruchu klientow.

Strategie partycjonowania wplywajace na wydajnosc pipeline'ow

Partycje sa fundamentalnym mechanizmem rownoleglego przetwarzania w Kafce. Kazdy topic jest dzielony na partycje, z ktorych kazda stanowi uporzadkowany, niemutowalny log. Wiadomosci w obrebie pojedynczej partycji zachowuja scisly porzadek sekwencyjny, natomiast miedzy partycjami nie istnieje zadna gwarancja kolejnosci globalnej.

Wybor klucza partycjonowania to jedna z najwazniejszych decyzji projektowych. Klucz determinuje, ktore wiadomosci trafiaja do ktorej partycji, co bezposrednio wplywa na kolejnosc przetwarzania, rozklad obciazenia i mozliwosci skalowania systemu.

python
# partition_strategy.py — Choosing the right partition key
from confluent_kafka import Producer
import json

def create_producer():
    return Producer({
        'bootstrap.servers': 'kafka-1:9092',
        'acks': 'all',
        'enable.idempotence': True,
        'max.in.flight.requests.per.connection': 5
    })

def publish_order_event(producer, event):
    # Key by customer_id: all events for one customer stay ordered
    key = str(event['customer_id']).encode('utf-8')
    value = json.dumps(event).encode('utf-8')
    producer.produce(
        topic='order-events',
        key=key,
        value=value,
        headers=[('source', b'order-service'), ('version', b'2')]
    )
    producer.flush()

def publish_clickstream(producer, event):
    # Key by session_id: ordering within a session matters
    # NOT by user_id — one user may have multiple sessions
    key = str(event['session_id']).encode('utf-8')
    value = json.dumps(event).encode('utf-8')
    producer.produce(
        topic='clickstream',
        key=key,
        value=value
    )

W powyzszym przykladzie zdarzenia zamowien sa partycjonowane po customer_id. Wszystkie zdarzenia dotyczace tego samego klienta trafiaja do jednej partycji, co gwarantuje ich przetwarzanie w kolejnosci chronologicznej. W przypadku danych clickstream klucz partycjonowania oparty jest na session_id (a nie user_id), poniewaz jeden uzytkownik moze prowadzic wiele sesji jednoczesnie. Dzieki temu porzadek zdarzen w ramach sesji zostaje zachowany, a obciazenie rozklada sie rownomiernie na wieksza liczbe partycji.

Konfiguracja enable.idempotence: True razem z acks: all aktywuje idempotentna produkcje, eliminujac duplikaty powstajace podczas ponownych prob przesylania po bledach sieciowych. Parametr max.in.flight.requests.per.connection: 5 umozliwia do pieciu rownoleglych zapytan z zachowaniem kolejnosci -- funkcjonalnosc dostepna od Kafka 2.0.

Uwaga na hot partitions

Zle dobrany klucz partycjonowania prowadzi do zjawiska hot partitions. Jesli np. 80% ruchu pochodzi od jednego klienta, a jego identyfikator sluzy jako klucz, jedna partycja przejmie wiekszosc wiadomosci i przeciazy odpowiedzialnego brokera. Zasada jest prosta: klucz partycjonowania powinien generowac rownomierny rozklad wartosci. W razie potrzeby mozna stosowac klucze zlozone (np. customer_id + data) lub niestandardowe partitionery.

Consumer Groups i zarzadzanie offsetami

Consumer groups umozliwiaja wielu instancjom jednego serwisu wspolne przetwarzanie topica. Kafka automatycznie przypisuje kazda partycje do dokladnie jednego konsumenta w grupie, co gwarantuje brak podwojnego przetwarzania w obrebie grupy. Liczba partycji topica wyznacza gorny limit rownoleglego przetwarzania -- jesli topic posiada 12 partycji, trzynasty konsument w grupie pozostanie bezczynny.

Kluczowym aspektem jest zarzadzanie offsetami, czyli pozycjami odczytu w poszczegolnych partycjach. Tryb domyslny (enable.auto.commit = True) zatwierdza offsety periodycznie, co w przypadku awarii moze powodowac podwojne przetwarzanie lub utrate wiadomosci. Dla pipeline'ow danych reczne zatwierdzanie offsetow po zakonczeniu przetwarzania stanowi wymog konieczny.

python
# consumer_pipeline.py — Consumer group with manual offset management
from confluent_kafka import Consumer, KafkaError
import json

def create_consumer(group_id: str):
    return Consumer({
        'bootstrap.servers': 'kafka-1:9092',
        'group.id': group_id,
        'auto.offset.reset': 'earliest',
        'enable.auto.commit': False,  # Manual commit for at-least-once
        'max.poll.interval.ms': 300000,
        'session.timeout.ms': 45000,
        'isolation.level': 'read_committed'  # Only read committed transactions
    })

def process_batch(consumer: Consumer, batch_size: int = 500):
    """Process records in micro-batches for throughput."""
    consumer.subscribe(['order-events'])
    buffer = []

    while True:
        msg = consumer.poll(timeout=1.0)
        if msg is None:
            continue
        if msg.error():
            if msg.error().code() == KafkaError._PARTITION_EOF:
                continue
            raise Exception(msg.error())

        record = json.loads(msg.value().decode('utf-8'))
        buffer.append(record)

        if len(buffer) >= batch_size:
            # Process the full batch (write to warehouse, etc.)
            write_to_warehouse(buffer)
            # Commit AFTER successful processing
            consumer.commit(asynchronous=False)
            buffer.clear()

def write_to_warehouse(records: list):
    # Insert batch into data warehouse (BigQuery, Snowflake, etc.)
    pass

Parametr isolation.level: read_committed blokuje odczyt wiadomosci z przerwanych transakcji. W polaczeniu z recznym synchronicznym zatwierdzaniem wzorzec ten gwarantuje semantyke at-least-once: kazda wiadomosc zostanie przetworzona co najmniej raz, nawet po restarcie konsumenta. Rozmiar mikro-paczki (500 rekordow) to kompromis miedzy latencja a przepustowoscia -- wieksza paczka zwieksza throughput, ale wydluza czas przetwarzania.

Parametr max.poll.interval.ms: 300000 (5 minut) definiuje maksymalny dopuszczalny odstep miedzy kolejnymi wywolaniami poll(). Przekroczenie tego limitu powoduje, ze Kafka uznaje konsumenta za martwego i uruchamia rebalancing grupy. Przy wolnych operacjach zapisu do hurtowni danych (np. BigQuery, Snowflake) wartosc ta wymaga starannego dostosowania.

Kafka Connect i integracja pipeline'ow danych

Change Data Capture (CDC) to jeden z najbardziej wartosciowych scenariuszy uzycia Kafki w kontekscie data engineeringu. Zamiast okresowego odpytywania bazy danych (polling), Debezium odczytuje dziennik transakcji (WAL w PostgreSQL, binlog w MySQL) i publikuje kazda zmiane jako zdarzenie w topicu Kafka. Podejscie to oferuje niemal zerowe opoznienie propagacji, brak dodatkowego obciazenia bazy zrodlowej oraz kompletne przechwytywanie wszystkich operacji, wlacznie z usuwaniem rekordow.

json
{
  "name": "postgres-cdc-source",
  "config": {
    "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
    "database.hostname": "postgres-primary",
    "database.port": "5432",
    "database.user": "replication_user",
    "database.dbname": "production",
    "topic.prefix": "cdc",
    "table.include.list": "public.orders,public.customers,public.products",
    "slot.name": "debezium_slot",
    "plugin.name": "pgoutput",
    "publication.name": "dbz_publication",
    "snapshot.mode": "initial",
    "transforms": "route",
    "transforms.route.type": "org.apache.kafka.connect.transforms.RegexRouter",
    "transforms.route.regex": "cdc\\.public\\.(.*)",
    "transforms.route.replacement": "warehouse.$1"
  }
}

Powysza konfiguracja Debezium przechwytuje zmiany w tabelach orders, customers i products produkcyjnej bazy PostgreSQL. Parametr snapshot.mode: initial powoduje wykonanie pelnej migawki aktualnego stanu tabel przed przejsciem w tryb strumieniowy przechwytywania zmian przyrostowych. Transformacja RegexRouter zmienia nazwy topicow wyjsciowych -- na przyklad cdc.public.orders staje sie warehouse.orders -- co upraszcza routing danych do hurtowni.

Slot replikacji (debezium_slot) gwarantuje, ze PostgreSQL nie usunie segmentow WAL, ktore nie zostaly jeszcze skonsumowane przez konektor. Monitorowanie rozmiaru slotu jest kluczowe: dluzsze przestoje konektora moga spowodowac zgromadzenie gigabajtow danych WAL i zagrozic pojemnosci dyskowej serwera bazodanowego.

Gotowy na rozmowy o Data Engineering?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

Semantyka exactly-once w pipeline'ach Kafka

Semantyka exactly-once stanowi najbardziej wymagajacy model gwarancji dostarczania w systemach strumieniowych. Bez niej pipeline naraza sie na generowanie duplikatow (at-least-once) lub utrate wiadomosci (at-most-once). Kafka realizuje exactly-once poprzez transakcje producenta w polaczeniu z izolacja read_committed po stronie konsumenta.

Wzorzec consume-transform-produce stanowi modelowy przyklad tego mechanizmu: konsument odczytuje wiadomosc ze zrodlowego topica, przeksztalca ja, produkuje wynik do topica docelowego i zatwierdza offset wiadomosci zrodlowej -- wszystko w obrebie jednej atomowej transakcji.

python
# exactly_once_pipeline.py — Transactional consume-transform-produce
from confluent_kafka import Consumer, Producer
import json

def transactional_pipeline():
    consumer = Consumer({
        'bootstrap.servers': 'kafka-1:9092',
        'group.id': 'etl-transformer',
        'enable.auto.commit': False,
        'isolation.level': 'read_committed'
    })

    producer = Producer({
        'bootstrap.servers': 'kafka-1:9092',
        'transactional.id': 'etl-transformer-001',
        'acks': 'all',
        'enable.idempotence': True
    })

    producer.init_transactions()
    consumer.subscribe(['raw-events'])

    while True:
        msg = consumer.poll(1.0)
        if msg is None or msg.error():
            continue

        # Begin atomic transaction
        producer.begin_transaction()
        try:
            raw = json.loads(msg.value())
            enriched = transform(raw)

            # Write transformed record to output topic
            producer.produce(
                'enriched-events',
                key=msg.key(),
                value=json.dumps(enriched).encode('utf-8')
            )

            # Commit consumer offset within the same transaction
            producer.send_offsets_to_transaction(
                consumer.position(consumer.assignment()),
                consumer.consumer_group_metadata()
            )
            producer.commit_transaction()
        except Exception:
            producer.abort_transaction()

def transform(record: dict) -> dict:
    # Apply business logic transformations
    record['processed_at'] = '2026-04-20T00:00:00Z'
    record['pipeline_version'] = '2.1'
    return record

Parametr transactional.id jednoznacznie identyfikuje instancje pipeline'u. W przypadku restartu Kafka wykorzystuje ten identyfikator do anulowania wszelkich niedokonczonych transakcji poprzedniej instancji, zapobiegajac tym samym duplikatom. Wywolanie send_offsets_to_transaction laczy zatwierdzenie offsetu konsumenta z ta sama transakcja co produkcja wzbogaconej wiadomosci -- albo obie operacje zostana zatwierdzone razem, albo zadna z nich nie wejdzie w zycie.

Nalezy pamietac, ze transakcje Kafka wprowadzaja dodatkowy narzut operacyjny. Kazda transakcja generuje zapisy do wewnetrznych topicow koordynacyjnych. Dla pipeline'ow o bardzo wysokiej przepustowosci (ponad 100 000 wiadomosci na sekunde) rekomendowane jest grupowanie wielu wiadomosci w jednej transakcji -- optymalnie od 100 do 500 rekordow na transakcje -- co zapewnia najlepszy kompromis miedzy gwarancjami dostarczania a wydajnoscia systemu.

Share Groups: semantyka kolejek w Kafka 4.2

Kafka 4.0 wprowadza Share Groups (KIP-932) -- fundamentalna zmiane w modelu konsumpcji. W klasycznych consumer groups kazda partycja jest przypisana wylacznie do jednego konsumenta. Share Groups zrywaja z tym ograniczeniem, pozwalajac wielu konsumentom na jednoczesny odczyt tej samej partycji z mechanizmem potwierdzania na poziomie pojedynczej wiadomosci.

Model ten czerpie inspiracje z tradycyjnych kolejek komunikatow (RabbitMQ, SQS), zachowujac jednoczesnie zalety infrastruktury Kafka. Jest szczegolnie przydatny w scenariuszach, gdzie kolejnosc przetwarzania nie jest krytyczna, ale rownomierne rozlozenie obciazenia miedzy konsumentami ma kluczowe znaczenie.

| Feature | Consumer Groups | Share Groups | |---|---|---| | Partition assignment | Exclusive (one consumer per partition) | Shared (multiple consumers per partition) | | Ordering guarantee | Per-partition ordering preserved | No ordering guarantee | | Use case | Ordered event streams | Task distribution, work queues | | Acknowledgement | Offset-based (commit position) | Per-record (acknowledge individually) | | Max parallelism | Limited by partition count | Limited by consumer count |

Share Groups nie zastepuja klasycznych consumer groups -- oba modele wspolistnieja i odpowiadaja na odmienne potrzeby. Dla pipeline'ow ETL, gdzie kolejnosc zdarzen jest kluczowa, klasyczne consumer groups pozostaja wlasciwym wyborem. Do dystrybucji zadan obliczeniowych -- generowania raportow, wysylki powiadomien, inferencji modeli ML -- Share Groups oferuja lepsza skalowalnosc, poniewaz stopien rownoleglego przetwarzania nie jest ograniczony liczba partycji, lecz liczba konsumentow.

Warto rowniez zwrocic uwage na mechanizm ponownego dostarczania (redelivery) na poziomie pojedynczej wiadomosci. Jesli konsument nie potwierdzi przetworzenia w okreslonym czasie, Kafka automatycznie przekazuje wiadomosc innemu konsumentowi w grupie. Zachowanie to jest analogiczne do visibility timeout w Amazon SQS i stanowi istotna roznice wzgledem offsetowego modelu klasycznych consumer groups.

Share Groups a istniejace pipeline'y

Share Groups stanowia uzupelnienie, a nie zastepstwo dla klasycznych consumer groups. Przy migracji istniejacych pipeline'ow nie nalezy automatycznie przechodzic na Share Groups. Nowy model sprawdza sie przede wszystkim w scenariuszach typu kolejka robocza -- przetwarzanie obrazow, wysylka powiadomien, generowanie raportow -- gdzie kolejnosc zdarzen nie ma znaczenia, a liczy sie elastyczna skalowalnosc.

Pytania rekrutacyjne o Kafka dla inzynierow danych

Rozmowy kwalifikacyjne na stanowiska z zakresu data engineeringu regularnie obejmuja zaawansowane pytania dotyczace Apache Kafka. Ponizsze pytania pokrywaja najczesciej weryfikowane zagadnienia w 2026 roku. Wiecej pytan cwiczeniowych dostepnych jest w zbiorze pytan rekrutacyjnych z data engineeringu.

W jaki sposob Kafka gwarantuje kolejnosc wiadomosci? Kolejnosc jest gwarantowana wylacznie w obrebie jednej partycji. Wszystkie wiadomosci z identycznym kluczem partycjonowania trafiaja do tej samej partycji i sa przetwarzane w kolejnosci produkcji. Miedzy roznymi partycjami nie istnieje zadna gwarancja porzadku. Aby uzyskac scisla globalna kolejnosc, konieczny jest topic z jedna partycja, co jednak eliminuje wszelka rownoleglosc przetwarzania.

Czym roznia sie semantyki at-least-once, at-most-once i exactly-once? At-most-once: konsument zatwierdza offset przed przetworzeniem wiadomosci; w przypadku awarii wiadomosc zostaje bezpowrotnie utracona. At-least-once: konsument zatwierdza offset po przetworzeniu; w razie awarii i restartu wiadomosc jest przetwarzana ponownie. Exactly-once: polaczenie transakcji producenta, idempotencji i izolacji read_committed gwarantuje, ze kazda wiadomosc zostanie przetworzona dokladnie raz, nawet w przypadku awarii poszczegolnych komponentow pipeline'u.

Jak dziala rebalancing w consumer group? Kiedy konsument dolacza do grupy lub ja opuszcza, Kafka inicjuje rebalancing. Podczas tej fazy wszystkie partycje sa tymczasowo zwalniane i ponownie przydzielane. Rebalancing powoduje chwilowe wstrzymanie przetwarzania dla calej grupy. Strategia Cooperative Sticky Assignor minimalizuje ten wplyw, ponownie przydzielajac wylacznie te partycje, ktore faktycznie wymagaja zmiany przypisania.

Co dzieje sie po awarii brokera hostujacego liderow partycji? Kafka wybiera nowych liderow sposrod zsynchronizowanych replik (ISR -- In-Sync Replicas). Producenci i konsumenci sa automatycznie przekierowywani do nowych liderow. Jesli po awarii warunek min.insync.replicas nie jest spelniony, produkcje z ustawieniem acks=all koncza sie bledem az do ponownej synchronizacji wystarczajacej liczby replik.

Kiedy wybrac Kafka Streams zamiast Apache Flink? Kafka Streams to biblioteka osadzana bezposrednio w aplikacji, niewymagajaca oddzielnego klastra przetwarzania. Nadaje sie do transformacji o umiarkowanej zlozonosci z przepustowoscia do kilkuset tysiecy wiadomosci na sekunde. Apache Flink to autonomiczny, rozproszony silnik przetwarzania strumieniowego, przeznaczony do zlozonych operacji (zaawansowane okienkowanie, Complex Event Processing, laczenia wielu strumieni) i ekstremalnych wolumenow danych. Decyzja zalezy od zlozonosci logiki przetwarzania i gotowosci zespolu do utrzymywania dodatkowej infrastruktury.

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Podsumowanie

Apache Kafka ugruntowala pozycje referencyjnej platformy strumieniowego przetwarzania danych w ekosystemie data engineeringu. Kluczowe wnioski z niniejszego artykulu:

  • Tryb KRaft (Kafka 4.x) calkowicie eliminuje ZooKeepera, upraszczajac wdrozenie i administracje klastrow bez utraty gwarancji niezawodnosci
  • Klucz partycjonowania determinuje kolejnosc przetwarzania, rozklad obciazenia i skalowalnosc -- zle dobrany klucz prowadzi do hot partitions degradujacych wydajnosc calego systemu
  • Reczne zatwierdzanie offsetow z enable.auto.commit: False i izolacja read_committed stanowia fundament niezawodnego pipeline'u at-least-once
  • CDC za posrednictwem Debezium i Kafka Connect przechwytuje zmiany w czasie rzeczywistym z WAL bazy zrodlowej, bez wplywu na jej wydajnosc operacyjna
  • Transakcje Kafka (exactly-once) gwarantuja atomowosc wzorca consume-transform-produce, przy narzucie kalibrowanym rozmiarem paczek transakcyjnych
  • Share Groups (KIP-932) w Kafka 4.x otwieraja nowy model konsumpcji przystosowany do dystrybucji zadan bez wymogu zachowania kolejnosci
  • Monitorowanie consumer lagu, partycji z nieszsynchronizowanymi replikami i opoznien zapytan jest niezbedne do zapobiegania cichym incydentom produkcyjnym

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Tagi

#kafka
#data-engineering
#streaming
#partitions

Udostępnij

Powiązane artykuły