Apache Kafka para Engenheiros de Dados: Particionamento, Consumer Groups e Pipelines de Streaming

Guia completo de Apache Kafka para engenharia de dados: arquitetura KRaft, estrategias de particionamento, consumer groups, CDC com Debezium, exactly-once semantics, Share Groups e perguntas de entrevista com exemplos praticos em Python.

Arquitetura de streaming do Apache Kafka com particoes e fluxo de dados para engenharia de dados

O Apache Kafka se tornou a espinha dorsal das plataformas de dados modernas. Presente em praticamente toda arquitetura de streaming em larga escala, o Kafka processa trilhoes de eventos diariamente em empresas como Nubank, iFood, Uber e Netflix. Para profissionais de engenharia de dados, compreender os mecanismos internos do Kafka -- particionamento, gerenciamento de offsets, garantias de entrega e os novos paradigmas do Kafka 4.x -- representa um diferencial competitivo tanto na construcao de pipelines robustos quanto em processos seletivos para vagas de alto nivel.

Kafka 4.x: adeus definitivo ao ZooKeeper

O Apache Kafka 4.0 removeu completamente o ZooKeeper da arquitetura. O modo KRaft (Kafka Raft) assume toda a gestao de metadados de forma nativa, eliminando um componente operacional inteiro e simplificando significativamente o deploy e a manutencao de clusters em producao.

Arquitetura KRaft: Como o Kafka 4.x Opera Sem ZooKeeper

O ZooKeeper acompanhou o Kafka desde sua criacao no LinkedIn, gerenciando metadados do cluster, eleicao de lideres e coordenacao de brokers. Contudo, essa dependencia externa adicionava complexidade operacional consideravel: um cluster ZooKeeper separado para administrar, monitorar e escalar. Com o KRaft, toda essa responsabilidade foi internalizada nos proprios brokers do Kafka.

No modo KRaft, cada no do cluster pode assumir o papel de broker, controller ou ambos. Os controllers formam um quorum Raft que gerencia os metadados do cluster -- informacoes sobre topicos, particoes, replicas e associacoes de membros. A eleicao de lider ocorre internamente via protocolo Raft, sem depender de nenhum sistema externo.

A configuracao a seguir demonstra um cluster Kafka 4.2 em modo KRaft utilizando Docker Compose. Cada no opera simultaneamente como broker e controller, um setup adequado para ambientes de desenvolvimento e testes.

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"

O parametro KAFKA_CONTROLLER_QUORUM_VOTERS define os nos participantes do quorum Raft, substituindo o papel que o ZooKeeper exercia anteriormente. Com fator de replicacao 3 e MIN_INSYNC_REPLICAS igual a 2, o cluster tolera a perda de um broker sem interrupcao de servico ou perda de dados. Em ambientes de producao, recomenda-se separar os papeis de broker e controller em nos dedicados para isolar a carga de metadados do processamento de mensagens.

Para equipes de engenharia de dados, a principal vantagem pratica do KRaft e operacional: menos componentes para administrar, recuperacao mais rapida em caso de falhas e capacidade de escalar para milhoes de particoes por cluster.

Estrategias de Particionamento para Pipelines de Dados

As particoes constituem a unidade fundamental de paralelismo no Kafka. Cada topico e dividido em uma ou mais particoes, e cada particao funciona como um log ordenado e imutavel de registros. A propriedade central da arquitetura do Kafka e que a ordenacao e garantida apenas dentro de uma mesma particao -- nao existe garantia de ordem entre particoes diferentes.

A escolha da chave de particao determina quais registros serao direcionados para qual particao. Registros com a mesma chave sempre vao para a mesma particao, o que preserva a ordem cronologica para aquela chave especifica. Essa decisao impacta diretamente tres aspectos criticos do pipeline: ordenacao do processamento, distribuicao de carga entre consumidores e capacidade de escalar horizontalmente.

Diretrizes para definir a quantidade de particoes:

  • O numero de particoes define o paralelismo maximo do consumer group -- se um topico tem 12 particoes, no maximo 12 consumidores operam em paralelo
  • Cada particao sustenta aproximadamente 10 MB/s de throughput de escrita em hardware convencional
  • Aumentar particoes de um topico existente redistribui as chaves e quebra temporariamente as garantias de ordem
  • O Kafka 4.x, gracas ao KRaft, gerencia milhares de particoes por broker com eficiencia superior as versoes anteriores
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
    )

No exemplo acima, eventos de pedidos utilizam customer_id como chave, garantindo que todas as operacoes de um mesmo cliente sejam processadas na ordem correta dentro da mesma particao. Para dados de clickstream, a chave session_id agrupa os eventos de uma sessao especifica, preservando a sequencia de acoes do usuario. Utilizar user_id nesse caso seria um erro, pois um unico usuario pode manter multiplas sessoes simultaneas, comprometendo a reconstrucao fiel da jornada em cada sessao.

A configuracao enable.idempotence: True combinada com acks: all ativa a producao idempotente. Isso significa que retentativas automaticas de envio -- causadas por timeouts de rede, por exemplo -- nao geram registros duplicados no topico.

Cuidado com hot partitions

Quando uma chave de particao concentra um volume desproporcionalmente alto de registros (por exemplo, um unico cliente corporativo responsavel por 80% dos eventos), a particao correspondente se transforma em gargalo. Essa situacao, conhecida como hot partition, sobrecarrega o broker responsavel e anula os beneficios do paralelismo. O monitoramento do lag por particao e essencial, e estrategias como chaves compostas ou particionadores customizados ajudam a mitigar esse problema.

Consumer Groups e Gerenciamento de Offsets

Os consumer groups representam o mecanismo central de processamento paralelo no Kafka. Dentro de um grupo, cada particao e atribuida exclusivamente a um consumidor. Isso garante que nenhum registro seja processado em duplicidade dentro do mesmo grupo, e o paralelismo maximo e igual ao numero de particoes do topico.

O gerenciamento de offsets -- a posicao de leitura em cada particao -- determina a semantica de entrega do pipeline. O Kafka armazena os offsets confirmados em um topico interno chamado __consumer_offsets. O modo padrao (enable.auto.commit=True) realiza o commit periodicamente, o que pode causar processamento duplicado ou perda de registros em caso de falha. Para pipelines de dados em producao, o commit manual apos o processamento efetivo e indispensavel.

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

O processamento em micro-batches (500 registros no exemplo) otimiza o throughput ao amortizar o custo de escrita no data warehouse. Cada lote e processado integralmente antes do commit sincrono do offset. Se o consumidor falhar apos o processamento porem antes do commit, os registros serao reprocessados na reinicializacao -- essa e a semantica at-least-once, o padrao mais seguro para pipelines que alimentam warehouses analiticos. Escritas idempotentes no destino (usando upserts, por exemplo) tratam eventuais duplicatas de forma transparente.

O parametro isolation.level: read_committed assegura que o consumidor leia apenas registros de transacoes confirmadas, ignorando dados de transacoes abortadas. A combinacao com session.timeout.ms: 45000 define a janela de deteccao de falha: se o coordinator nao receber heartbeats nesse intervalo, o consumidor e considerado inativo e um rebalanceamento e disparado.

Kafka Connect e Change Data Capture com Debezium

O Kafka Connect fornece um framework robusto e escalavel para movimentacao de dados entre o Kafka e sistemas externos. Source connectors ingerem dados para dentro do Kafka; sink connectors exportam dados para sistemas de destino como data warehouses, data lakes ou bancos de dados analiticos.

O Change Data Capture (CDC) e um dos padroes mais poderosos em engenharia de dados. Em vez de realizar consultas periodicas ao banco de dados de origem (polling), o CDC captura cada alteracao diretamente do log de transacoes (WAL no PostgreSQL, binlog no MySQL) e publica como evento no Kafka. O Debezium e o conector CDC mais utilizado no ecossistema, com suporte a PostgreSQL, MySQL, MongoDB e diversos outros bancos.

As vantagens do CDC sobre o polling tradicional sao substanciais: latencia de captura inferior a 100 milissegundos, nenhuma carga adicional no banco de origem (a leitura do WAL e passiva) e captura completa de todas as operacoes, incluindo DELETEs que o polling por timestamp nao consegue detectar sem mecanismos de soft delete.

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

Essa configuracao Debezium captura cada INSERT, UPDATE e DELETE das tabelas orders, customers e products de um PostgreSQL de producao. O snapshot.mode: initial realiza primeiro uma captura completa do estado atual das tabelas e depois muda para streaming de alteracoes incrementais. A transformacao RegexRouter renomeia os topicos de cdc.public.orders para warehouse.orders, mantendo a nomenclatura do pipeline organizada e semanticamente clara. Combinado com um sink connector para BigQuery, Snowflake ou Redshift, esse setup cria um pipeline CDC totalmente automatizado sem nenhuma linha de codigo customizado.

Monitoramento do replication slot

O replication slot (debezium_slot) garante que o PostgreSQL nao descarte segmentos WAL antes do Debezium consumi-los. Porem, se o conector parar por um periodo prolongado, o slot acumula gigabytes de WAL e pode comprometer o espaco em disco do servidor. Configurar alertas sobre o tamanho do slot e definir max_slot_wal_keep_size no PostgreSQL previne incidentes em producao.

Pronto para mandar bem nas entrevistas de Data Engineering?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Semantica Exactly-Once em Pipelines Kafka

O Kafka implementa semantica exactly-once (EOS) por meio da combinacao de produtores idempotentes e APIs transacionais. Para pipelines de engenharia de dados, a EOS elimina a necessidade de logica de deduplicacao em sistemas downstream, garantindo que cada registro seja processado exatamente uma vez.

O mecanismo se apoia em tres pilares:

  1. Produtor idempotente: Cada produtor recebe um Producer ID unico do broker. O broker utiliza numeros sequenciais para detectar e descartar retentativas duplicadas, garantindo que falhas de rede nunca gerem registros duplicados.
  2. Transacoes atomicas: Um produtor pode escrever em multiplas particoes e confirmar offsets do consumidor dentro de uma unica transacao. Todas as escritas sao efetivadas atomicamente, ou nenhuma e.
  3. Isolamento read_committed: Consumidores configurados com isolation.level=read_committed enxergam apenas registros de transacoes efetivadas com sucesso, ignorando completamente dados de transacoes abortadas.

O padrao consume-transform-produce ilustra a EOS na pratica: o consumidor le um registro, aplica transformacoes de negocios e produz o resultado em um topico de saida, tudo dentro de uma transacao atomica.

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

O transactional.id identifica de forma unica cada instancia do pipeline. Quando um consumidor falha e uma nova instancia e iniciada com o mesmo transactional.id, o broker aborta automaticamente quaisquer transacoes pendentes da instancia anterior, prevenindo saida duplicada. Cada instancia concorrente do pipeline deve possuir um transactional.id distinto.

Custo de latencia das transacoes

Transacoes Kafka adicionam tipicamente entre 5 e 15 ms de latencia por commit. Para pipelines de alto throughput, o processamento em micro-batches dentro de uma unica transacao amortiza esse custo de forma eficiente. Agrupar entre 100 e 500 registros por transacao oferece o melhor equilibrio entre garantias de entrega e desempenho.

Share Groups: Semantica de Filas no Kafka 4.2

O Kafka 4.2 disponibiliza os Share Groups em versao production-ready, introduzindo semantica de fila de mensagens tradicional ao ecossistema Kafka. Nos consumer groups classicos, cada particao e atribuida exclusivamente a um consumidor. Nos Share Groups, multiplos consumidores podem processar registros da mesma particao de forma independente, com confirmacao individual por registro.

Esse modelo resolve uma limitacao historica do Kafka: a impossibilidade de escalar consumidores alem do numero de particoes. Para cargas de trabalho como envio de notificacoes, processamento de uploads, execucao de inferencia de modelos de machine learning ou qualquer cenario onde a ordenacao nao e relevante, os Share Groups eliminam a necessidade de sistemas de filas auxiliares ao lado do Kafka.

| Caracteristica | Consumer Groups | Share Groups | |---|---|---| | Atribuicao de particoes | Exclusiva (um consumidor por particao) | Compartilhada (multiplos consumidores por particao) | | Garantia de ordenacao | Ordenacao por particao preservada | Sem garantia de ordenacao | | Caso de uso | Streams de eventos ordenados, CDC, ETL | Distribuicao de tarefas, filas de trabalho | | Confirmacao | Baseada em offset (commit de posicao) | Por registro (acknowledgment individual) | | Paralelismo maximo | Limitado pela quantidade de particoes | Limitado pela quantidade de consumidores | | Rebalanceamento | Redistribui particoes inteiras | Nao requer rebalanceamento |

Quando utilizar Share Groups

Os Share Groups sao ideais para workloads de distribuicao de tarefas: envio de e-mails, processamento de imagens, execucao de jobs de ML, validacoes independentes. Para event sourcing, CDC ou qualquer pipeline que exija processamento ordenado, os consumer groups tradicionais continuam sendo a escolha adequada. Os dois modelos coexistem no mesmo cluster.

Perguntas de Entrevista sobre Kafka para Engenheiros de Dados

Processos seletivos para vagas de engenharia de dados avaliam com frequencia o conhecimento aprofundado sobre Kafka. As perguntas a seguir cobrem os temas mais recorrentes em entrevistas tecnicas.

P: Como o Kafka garante a ordenacao das mensagens? A ordenacao e garantida exclusivamente dentro de uma unica particao. Todos os registros com a mesma chave de particao sao direcionados para a mesma particao e adicionados em sequencia. Entre particoes diferentes, nao ha nenhuma garantia de ordem. A escolha criteriosa da chave de particao e o principal mecanismo para assegurar a ordenacao em um pipeline Kafka.

P: Qual a diferenca entre at-least-once e exactly-once? Na semantica at-least-once, o consumidor confirma o offset somente apos o processamento. Se ocorrer uma falha entre o processamento e o commit, os registros serao reprocessados na reinicializacao. Na semantica exactly-once, as APIs transacionais envolvem o ciclo completo de consumo, transformacao e producao em uma operacao atomica -- o offset do consumidor e a producao do registro transformado sao confirmados na mesma transacao.

P: O que acontece quando um consumidor do grupo falha? O coordinator do grupo detecta a ausencia de heartbeats (controlada por session.timeout.ms) e dispara um rebalanceamento. As particoes do consumidor inativo sao redistribuidas entre os membros restantes. O Cooperative Sticky Assignor, padrao no Kafka 4.x, minimiza a interrupcao ao reatribuir apenas as particoes afetadas, em vez de redistribuir todas.

P: Explique a diferenca entre acks=1 e acks=all. Com acks=1, o broker confirma a escrita assim que a replica lider persiste o registro localmente. Com acks=all, o broker aguarda ate que todas as replicas in-sync (ISR) confirmem a escrita. A configuracao acks=all combinada com min.insync.replicas=2 garante zero perda de dados em caso de falha de um broker individual, ao custo de uma latencia ligeiramente maior.

P: Como projetar um pipeline CDC com Kafka? Capturar alteracoes do banco de origem com Debezium, que le o log de transacoes (WAL/binlog) e publica eventos em topicos Kafka particionados pela chave primaria. Utilizar um sink connector para carregar as alteracoes no data warehouse. Configurar isolation.level=read_committed nos consumidores para evitar leitura de transacoes abortadas. Utilizar o Schema Registry para gerenciar a evolucao de schemas dos eventos de mudanca.

P: O que e ISR e por que e importante? ISR (In-Sync Replicas) e o conjunto de replicas totalmente atualizadas em relacao ao lider da particao. Somente membros do ISR sao elegiveis para eleicao de lider. A configuracao min.insync.replicas define quantas replicas devem confirmar uma escrita para que ela seja considerada efetivada. Se o ISR diminuir abaixo desse valor, o broker rejeita novas escritas em vez de arriscar perda de dados.

P: Qual a diferenca entre Consumer Groups e Share Groups? Nos consumer groups, cada particao e atribuida exclusivamente a um consumidor, preservando a ordenacao por particao. O paralelismo maximo e limitado pelo numero de particoes. Nos Share Groups (Kafka 4.2), multiplos consumidores processam registros da mesma particao de forma concorrente, sem garantia de ordenacao. Share Groups sao adequados para cargas de distribuicao de tarefas, enquanto consumer groups sao essenciais para pipelines que dependem de processamento ordenado.

Conclusao

  • O Kafka 4.x com KRaft elimina definitivamente o ZooKeeper, reduzindo a complexidade operacional e simplificando deploys para equipes de engenharia de dados
  • A selecao da chave de particao define as garantias de ordenacao do pipeline -- a escolha deve refletir os requisitos de processamento downstream, nao conveniencia
  • Commits manuais de offset com enable.auto.commit=False habilitam entrega at-least-once; as APIs transacionais fornecem semantica exactly-once para pipelines consume-transform-produce
  • O Kafka Connect com Debezium viabiliza CDC production-ready, capturando alteracoes em tempo real sem codigo customizado e sem impacto no banco de origem
  • Os Share Groups (Kafka 4.2) adicionam semantica de filas para workloads de distribuicao de tarefas onde a ordenacao nao e necessaria
  • A escalabilidade dos consumer groups e limitada pela quantidade de particoes -- o dimensionamento deve considerar o pico de paralelismo de consumidores
  • O dominio dos mecanismos internos do Kafka -- particionamento, offsets, transacoes e rebalanceamento -- e avaliado rotineiramente em entrevistas tecnicas para posicoes de engenharia de dados

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Tags

#kafka
#streaming
#data-engineering
#partitions
#consumer-groups
#kraft
#event-driven

Compartilhar

Artigos relacionados